diff --git a/build/Base.props b/build/Base.props
index c2fc11c94b..2d50a7eae0 100644
--- a/build/Base.props
+++ b/build/Base.props
@@ -1,5 +1,6 @@
-
+
+
diff --git a/build/System.Memory.props b/build/System.Memory.props
index a413e18927..35a87a3a2f 100644
--- a/build/System.Memory.props
+++ b/build/System.Memory.props
@@ -1,5 +1,6 @@
-
+
+
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index d5e5cb14dc..e55f003133 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -55,8 +55,7 @@ namespace ControlCatalog.NetCore
return builder
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
- UseHeadlessDrawing = true,
- UseCompositor = true
+ UseHeadlessDrawing = true
})
.AfterSetup(_ =>
{
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 4c3c211ca5..c39e9f0a81 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -1,7 +1,9 @@
+ xmlns:pages="clr-namespace:ControlCatalog.Pages"
+ x:Class="ControlCatalog.Pages.DataGridPage"
+ x:DataType="pages:DataGridPage">
@@ -33,7 +35,7 @@
-
+
-
+
+
-
-
-
-
+
+
+
+
-
+
-
+
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
index 3565d113bc..b0c3e3a553 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
@@ -48,20 +49,22 @@ namespace ControlCatalog.Pages
var dg3 = this.Get("dataGridEdit");
dg3.IsReadOnly = false;
- var items = new List
+ var list = new ObservableCollection
{
new Person { FirstName = "John", LastName = "Doe" , Age = 30},
new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 },
new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 }
};
- var collectionView3 = new DataGridCollectionView(items);
-
- dg3.Items = collectionView3;
+ DataGrid3Source = list;
var addButton = this.Get
diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs
index 502da52597..50d7cc5dc5 100644
--- a/src/Avalonia.Base/Input/PointerEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerEventArgs.cs
@@ -127,7 +127,9 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
- internal PointerPressedEventArgs(
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
+ public PointerPressedEventArgs(
object source,
IPointer pointer,
Visual rootVisual, Point rootVisualPosition,
@@ -146,7 +148,9 @@ namespace Avalonia.Input
public class PointerReleasedEventArgs : PointerEventArgs
{
- internal PointerReleasedEventArgs(
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
+ public PointerReleasedEventArgs(
object source, IPointer pointer,
Visual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers,
@@ -167,7 +171,9 @@ namespace Avalonia.Input
{
public IPointer Pointer { get; }
- internal PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")]
+ public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
{
Pointer = pointer;
Source = source;
diff --git a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
index 09716b4246..36ee1fff27 100644
--- a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
+++ b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
@@ -105,5 +105,13 @@ namespace Avalonia.Input.TextInput
UnsubscribeFromParents();
UpdateMatrix();
}
+
+ public static IDisposable Track(Visual visual, Action cb)
+ {
+ var rv = new TransformTrackingHelper();
+ rv.MatrixChanged += () => cb(visual, rv.Matrix);
+ rv.SetVisual(visual);
+ return rv;
+ }
}
}
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index 242d70821a..c4742bcba4 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -3,8 +3,9 @@ using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Logging;
+using Avalonia.Rendering;
using Avalonia.Threading;
-using Avalonia.VisualTree;
+using Avalonia.Utilities;
#nullable enable
@@ -24,6 +25,7 @@ namespace Avalonia.Layout
private bool _disposed;
private bool _queued;
private bool _running;
+ private int _totalPassCount;
public LayoutManager(ILayoutRoot owner)
{
@@ -33,6 +35,8 @@ namespace Avalonia.Layout
public virtual event EventHandler? LayoutUpdated;
+ internal Action? LayoutPassTimed { get; set; }
+
///
public virtual void InvalidateMeasure(Layoutable control)
{
@@ -116,10 +120,9 @@ namespace Avalonia.Layout
if (!_running)
{
- Stopwatch? stopwatch = null;
-
const LogEventLevel timingLogLevel = LogEventLevel.Information;
- bool captureTiming = Logger.IsEnabled(timingLogLevel, LogArea.Layout);
+ var captureTiming = LayoutPassTimed is not null || Logger.IsEnabled(timingLogLevel, LogArea.Layout);
+ var startingTimestamp = 0L;
if (captureTiming)
{
@@ -129,8 +132,7 @@ namespace Avalonia.Layout
_toMeasure.Count,
_toArrange.Count);
- stopwatch = new Stopwatch();
- stopwatch.Start();
+ startingTimestamp = Stopwatch.GetTimestamp();
}
_toMeasure.BeginLoop(MaxPasses);
@@ -139,6 +141,7 @@ namespace Avalonia.Layout
try
{
_running = true;
+ ++_totalPassCount;
for (var pass = 0; pass < MaxPasses; ++pass)
{
@@ -160,9 +163,10 @@ namespace Avalonia.Layout
if (captureTiming)
{
- stopwatch!.Stop();
+ var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
+ LayoutPassTimed?.Invoke(new LayoutPassTiming(_totalPassCount, elapsed));
- Logger.TryGet(timingLogLevel, LogArea.Layout)?.Log(this, "Layout pass finished in {Time}", stopwatch.Elapsed);
+ Logger.TryGet(timingLogLevel, LogArea.Layout)?.Log(this, "Layout pass finished in {Time}", elapsed);
}
}
diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
index ce38fc5abc..6577532891 100644
--- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Utilities;
@@ -10,6 +12,7 @@ namespace Avalonia.Media.Imaging
///
public class Bitmap : IBitmap
{
+ private bool _isTranscoded;
///
/// Loads a Bitmap from a stream and decodes at the desired width. Aspect ratio is maintained.
/// This is more efficient than loading and then resizing.
@@ -100,7 +103,28 @@ namespace Avalonia.Media.Imaging
/// The number of bytes per row.
public Bitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
- PlatformImpl = RefCountable.Create(GetFactory().LoadBitmap(format, alphaFormat, data, size, dpi, stride));
+ var factory = GetFactory();
+ if (factory.IsSupportedBitmapPixelFormat(format))
+ PlatformImpl = RefCountable.Create(factory.LoadBitmap(format, alphaFormat, data, size, dpi, stride));
+ else
+ {
+ var transcoded = Marshal.AllocHGlobal(size.Width * size.Height * 4);
+ var transcodedStride = size.Width * 4;
+ try
+ {
+ PixelFormatReader.Transcode(transcoded, data, size, stride, transcodedStride, format);
+ var transcodedAlphaFormat = format.HasAlpha ? alphaFormat : AlphaFormat.Opaque;
+
+ PlatformImpl = RefCountable.Create(factory.LoadBitmap(PixelFormat.Rgba8888, transcodedAlphaFormat,
+ transcoded, size, dpi, transcodedStride));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(transcoded);
+ }
+
+ _isTranscoded = true;
+ }
}
///
@@ -145,6 +169,57 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Item.Save(stream, quality);
}
+ public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format;
+
+ protected internal unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride,
+ ILockedFramebuffer fb)
+ {
+ if ((sourceRect.Width <= 0 || sourceRect.Height <= 0) && (sourceRect.X != 0 || sourceRect.Y != 0))
+ throw new ArgumentOutOfRangeException(nameof(sourceRect));
+
+ if (sourceRect.X < 0 || sourceRect.Y < 0)
+ throw new ArgumentOutOfRangeException(nameof(sourceRect));
+
+ if (sourceRect.Width <= 0)
+ sourceRect = sourceRect.WithWidth(PixelSize.Width);
+ if (sourceRect.Height <= 0)
+ sourceRect = sourceRect.WithHeight(PixelSize.Height);
+
+ if (sourceRect.Right > PixelSize.Width || sourceRect.Bottom > PixelSize.Height)
+ throw new ArgumentOutOfRangeException(nameof(sourceRect));
+
+ int minStride = checked(((sourceRect.Width * fb.Format.BitsPerPixel) + 7) / 8);
+ if (stride < minStride)
+ throw new ArgumentOutOfRangeException(nameof(stride));
+
+ var minBufferSize = stride * sourceRect.Height;
+ if (minBufferSize > bufferSize)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize));
+
+ for (var y = 0; y < sourceRect.Height; y++)
+ {
+ var srcAddress = fb.Address + fb.RowBytes * y;
+ var dstAddress = buffer + stride * y;
+ Unsafe.CopyBlock(dstAddress.ToPointer(), srcAddress.ToPointer(), (uint)minStride);
+ }
+ }
+
+ public virtual void CopyPixels(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride)
+ {
+ if (
+ Format == null
+ || PlatformImpl.Item is not IReadableBitmapImpl readable
+ || Format != readable.Format
+ )
+ throw new NotSupportedException("CopyPixels is not supported for this bitmap type");
+
+ if (_isTranscoded)
+ throw new NotSupportedException("CopyPixels is not supported for transcoded bitmaps");
+
+ using (var fb = readable.Lock())
+ CopyPixelsCore(sourceRect, buffer, bufferSize, stride, fb);
+ }
+
///
void IImage.Draw(
DrawingContext context,
diff --git a/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs b/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
new file mode 100644
index 0000000000..68ae2e37a5
--- /dev/null
+++ b/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Imaging;
+
+internal class BitmapMemory : IDisposable
+{
+ private readonly int _memorySize;
+
+ public BitmapMemory(PixelFormat format, PixelSize size)
+ {
+ Format = format;
+ Size = size;
+ RowBytes = (size.Width * format.BitsPerPixel + 7) / 8;
+ _memorySize = RowBytes * size.Height;
+ Address = Marshal.AllocHGlobal(_memorySize);
+ GC.AddMemoryPressure(_memorySize);
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ if (Address != IntPtr.Zero)
+ {
+ GC.RemoveMemoryPressure(_memorySize);
+ Marshal.FreeHGlobal(Address);
+ }
+ }
+
+ public void Dispose()
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ~BitmapMemory()
+ {
+ ReleaseUnmanagedResources();
+ }
+
+ public IntPtr Address { get; private set; }
+ public PixelSize Size { get; }
+ public int RowBytes { get; }
+ public PixelFormat Format { get; }
+
+
+
+ public void CopyToRgba(IntPtr buffer, int rowBytes) =>
+ PixelFormatReader.Transcode(buffer, Address, Size, RowBytes, rowBytes, Format);
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
new file mode 100644
index 0000000000..fc7c174ed6
--- /dev/null
+++ b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
@@ -0,0 +1,280 @@
+using System;
+using Avalonia.Platform;
+namespace Avalonia.Media.Imaging;
+
+internal struct Rgba8888Pixel
+{
+ public byte R;
+ public byte G;
+ public byte B;
+ public byte A;
+}
+
+static unsafe class PixelFormatReader
+{
+ public interface IPixelFormatReader
+ {
+ Rgba8888Pixel ReadNext();
+ void Reset(IntPtr address);
+ }
+
+ private static readonly Rgba8888Pixel s_white = new Rgba8888Pixel
+ {
+ A = 255,
+ B = 255,
+ G = 255,
+ R = 255
+ };
+
+ private static readonly Rgba8888Pixel s_black = new Rgba8888Pixel
+ {
+ A = 255,
+ B = 0,
+ G = 0,
+ R = 0
+ };
+
+ public unsafe struct BlackWhitePixelReader : IPixelFormatReader
+ {
+ private int _bit;
+ private byte* _address;
+
+ public void Reset(IntPtr address)
+ {
+ _address = (byte*)address;
+ _bit = 0;
+ }
+
+ public Rgba8888Pixel ReadNext()
+ {
+ var shift = 7 - _bit;
+ var value = (*_address >> shift) & 1;
+ _bit++;
+ if (_bit == 8)
+ {
+ _address++;
+ _bit = 0;
+ }
+ return value == 1 ? s_white : s_black;
+ }
+ }
+
+ public unsafe struct Gray2PixelReader : IPixelFormatReader
+ {
+ private int _bit;
+ private byte* _address;
+
+ public void Reset(IntPtr address)
+ {
+ _address = (byte*)address;
+ _bit = 0;
+ }
+
+ private static Rgba8888Pixel[] Palette = new[]
+ {
+ s_black,
+ new Rgba8888Pixel
+ {
+ A = 255, B = 0x55, G = 0x55, R = 0x55
+ },
+ new Rgba8888Pixel
+ {
+ A = 255, B = 0xAA, G = 0xAA, R = 0xAA
+ },
+ s_white
+ };
+
+ public Rgba8888Pixel ReadNext()
+ {
+ var shift = 6 - _bit;
+ var value = (byte)((*_address >> shift));
+ value = (byte)((value & 3));
+ _bit += 2;
+ if (_bit == 8)
+ {
+ _address++;
+ _bit = 0;
+ }
+
+ return Palette[value];
+ }
+ }
+
+ public unsafe struct Gray4PixelReader : IPixelFormatReader
+ {
+ private int _bit;
+ private byte* _address;
+
+ public void Reset(IntPtr address)
+ {
+ _address = (byte*)address;
+ _bit = 0;
+ }
+
+ public Rgba8888Pixel ReadNext()
+ {
+ var shift = 4 - _bit;
+ var value = (byte)((*_address >> shift));
+ value = (byte)((value & 0xF));
+ value = (byte)(value | (value << 4));
+ _bit += 4;
+ if (_bit == 8)
+ {
+ _address++;
+ _bit = 0;
+ }
+
+ return new Rgba8888Pixel
+ {
+ A = 255,
+ B = value,
+ G = value,
+ R = value
+ };
+ }
+ }
+
+ public unsafe struct Gray8PixelReader : IPixelFormatReader
+ {
+ private byte* _address;
+ public void Reset(IntPtr address)
+ {
+ _address = (byte*)address;
+ }
+
+ public Rgba8888Pixel ReadNext()
+ {
+ var value = *_address;
+ _address++;
+
+ return new Rgba8888Pixel
+ {
+ A = 255,
+ B = value,
+ G = value,
+ R = value
+ };
+ }
+ }
+
+ public unsafe struct Gray16PixelReader : IPixelFormatReader
+ {
+ private ushort* _address;
+ public Rgba8888Pixel ReadNext()
+ {
+ var value16 = *_address;
+ _address++;
+ var value8 = (byte)(value16 >> 8);
+ return new Rgba8888Pixel
+ {
+ A = 255,
+ B = value8,
+ G = value8,
+ R = value8
+ };
+ }
+
+ public void Reset(IntPtr address) => _address = (ushort*)address;
+ }
+
+ public unsafe struct Gray32FloatPixelReader : IPixelFormatReader
+ {
+ private byte* _address;
+ public Rgba8888Pixel ReadNext()
+ {
+ var f = *(float*)_address;
+ var srgb = Math.Pow(f, 1 / 2.2);
+ var value = (byte)(srgb * 255);
+
+ _address += 4;
+ return new Rgba8888Pixel
+ {
+ A = 255,
+ B = value,
+ G = value,
+ R = value
+ };
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ struct Rgba64
+ {
+#pragma warning disable CS0649
+ public ushort R;
+ public ushort G;
+ public ushort B;
+ public ushort A;
+#pragma warning restore CS0649
+ }
+
+ public unsafe struct Rgba64PixelFormatReader : IPixelFormatReader
+ {
+ private Rgba64* _address;
+ public Rgba8888Pixel ReadNext()
+ {
+ var value = *_address;
+
+ _address++;
+ return new Rgba8888Pixel
+ {
+ A = (byte)(value.A >> 8),
+ B = (byte)(value.B >> 8),
+ G = (byte)(value.G >> 8),
+ R = (byte)(value.R >> 8),
+ };
+ }
+
+ public void Reset(IntPtr address) => _address = (Rgba64*)address;
+ }
+
+ public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
+ PixelFormat format)
+ {
+ if (format == PixelFormats.BlackWhite)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray2)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray4)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray8)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray16)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray32Float)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Rgba64)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else
+ throw new NotSupportedException($"Pixel format {format} is not supported");
+ }
+
+ public static bool SupportsFormat(PixelFormat format)
+ {
+ return format == PixelFormats.BlackWhite
+ || format == PixelFormats.Gray2
+ || format == PixelFormats.Gray4
+ || format == PixelFormats.Gray8
+ || format == PixelFormats.Gray16
+ || format == PixelFormats.Gray32Float
+ || format == PixelFormats.Rgba64;
+ }
+
+ public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst) where TReader : struct, IPixelFormatReader
+ {
+ var w = size.Width;
+ var h = size.Height;
+ TReader reader = default;
+ for (var y = 0; y < h; y++)
+ {
+ reader.Reset(src + strideSrc * y);
+ var dstRow = (Rgba8888Pixel*)(dst + strideDst * y);
+ for (var x = 0; x < w; x++)
+ {
+ *dstRow = reader.ReadNext();
+ dstRow++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
index 1aac8efac7..868f4439c4 100644
--- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Runtime.CompilerServices;
using Avalonia.Platform;
namespace Avalonia.Media.Imaging
@@ -9,7 +10,9 @@ namespace Avalonia.Media.Imaging
///
public class WriteableBitmap : Bitmap
{
-
+ // Holds a buffer with pixel format that requires transcoding
+ private BitmapMemory? _pixelFormatMemory = null;
+
///
/// Initializes a new instance of the class.
///
@@ -19,16 +22,67 @@ namespace Avalonia.Media.Imaging
/// The alpha format (optional).
/// An .
public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null)
- : base(CreatePlatformImpl(size, dpi, format, alphaFormat))
+ : this(CreatePlatformImpl(size, dpi, format, alphaFormat))
{
}
- private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl)
+ private WriteableBitmap((IBitmapImpl impl, BitmapMemory? mem) bitmapWithMem) : this(bitmapWithMem.impl,
+ bitmapWithMem.mem)
{
}
+
+ private WriteableBitmap(IBitmapImpl impl, BitmapMemory? pixelFormatMemory = null) : base(impl)
+ {
+ _pixelFormatMemory = pixelFormatMemory;
+ }
+
+ ///
+ /// Initializes a new instance of the class with existing pixel data
+ /// The data is copied to the bitmap
+ ///
+ /// The pixel format.
+ /// The alpha format.
+ /// The pointer to the source bytes.
+ /// The size of the bitmap in device pixels.
+ /// The DPI of the bitmap.
+ /// The number of bytes per row.
+ public unsafe WriteableBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
+ : this(size, dpi, format, alphaFormat)
+ {
+ var minStride = (format.BitsPerPixel * size.Width + 7) / 8;
+ if (minStride > stride)
+ throw new ArgumentOutOfRangeException(nameof(stride));
- public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock();
+ using (var locked = Lock())
+ {
+ for (var y = 0; y < size.Height; y++)
+ Unsafe.CopyBlock((locked.Address + locked.RowBytes * y).ToPointer(),
+ (data + y * stride).ToPointer(), (uint)minStride);
+ }
+ }
+
+ public override PixelFormat? Format => _pixelFormatMemory?.Format ?? base.Format;
+
+ public ILockedFramebuffer Lock()
+ {
+ if (_pixelFormatMemory == null)
+ return ((IWriteableBitmapImpl)PlatformImpl.Item).Lock();
+
+ return new LockedFramebuffer(_pixelFormatMemory.Address, _pixelFormatMemory.Size,
+ _pixelFormatMemory.RowBytes,
+ Dpi, _pixelFormatMemory.Format, () =>
+ {
+ using var inner = ((IWriteableBitmapImpl)PlatformImpl.Item).Lock();
+ _pixelFormatMemory.CopyToRgba(inner.Address, inner.RowBytes);
+ });
+ }
+
+ public override void CopyPixels(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride)
+ {
+ using (var fb = Lock())
+ CopyPixelsCore(sourceRect, buffer, bufferSize, stride, fb);
+ }
public static WriteableBitmap Decode(Stream stream)
{
@@ -67,14 +121,25 @@ namespace Avalonia.Media.Imaging
return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode));
}
- private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
+ private static (IBitmapImpl, BitmapMemory?) CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
{
+ if (size.Width <= 0 || size.Height <= 0)
+ throw new ArgumentException("Size should be >= (1,1)", nameof(size));
+
var ri = GetFactory();
PixelFormat finalFormat = format ?? ri.DefaultPixelFormat;
AlphaFormat finalAlphaFormat = alphaFormat ?? ri.DefaultAlphaFormat;
- return ri.CreateWriteableBitmap(size, dpi, finalFormat, finalAlphaFormat);
+ if (ri.IsSupportedBitmapPixelFormat(finalFormat))
+ return (ri.CreateWriteableBitmap(size, dpi, finalFormat, finalAlphaFormat), null);
+
+ if (!PixelFormatReader.SupportsFormat(finalFormat))
+ throw new NotSupportedException($"Pixel format {finalFormat} is not supported");
+
+ var impl = ri.CreateWriteableBitmap(size, dpi, PixelFormat.Rgba8888,
+ finalFormat.HasAlpha ? finalAlphaFormat : AlphaFormat.Opaque);
+ return (impl, new BitmapMemory(finalFormat, size));
}
private static IPlatformRenderInterface GetFactory()
diff --git a/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs b/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs
new file mode 100644
index 0000000000..6bb820d214
--- /dev/null
+++ b/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Avalonia.Metadata;
+
+///
+/// Instructs the compiler to resolve the compiled bindings data type for the item-specific properties of collection-like controls.
+///
+///
+/// A typical usage example is a ListBox control, where is defined on the ItemTemplate property,
+/// allowing the template to inherit the data type from the Items collection binding.
+///
+[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+public sealed class InheritDataTypeFromItemsAttribute : Attribute
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the property whose item type should be used on the target property.
+ public InheritDataTypeFromItemsAttribute(string ancestorItemsProperty)
+ {
+ AncestorItemsProperty = ancestorItemsProperty;
+ }
+
+ ///
+ /// The name of the property whose item type should be used on the target property.
+ ///
+ public string AncestorItemsProperty { get; }
+
+ ///
+ /// The ancestor type to be used in a lookup for the .
+ /// If null, the declaring type of the target property is used.
+ ///
+ public Type? AncestorType { get; set; }
+}
diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs
index 469f33e7fd..ef207a3dae 100644
--- a/src/Avalonia.Base/PixelRect.cs
+++ b/src/Avalonia.Base/PixelRect.cs
@@ -351,7 +351,7 @@ namespace Avalonia
/// The new .
public PixelRect WithHeight(int height)
{
- return new PixelRect(X, Y, Width, Height);
+ return new PixelRect(X, Y, Width, height);
}
///
diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
index 66d4f083c2..1778031e5b 100644
--- a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
+++ b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
@@ -23,5 +23,4 @@ public static class OptionalFeatureProviderExtensions
rv = provider.TryGetFeature();
return rv != null;
}
-
}
diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
index 41e792d58e..cfc7fac3ea 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -197,6 +197,8 @@ namespace Avalonia.Platform
/// Default used on this platform.
///
public PixelFormat DefaultPixelFormat { get; }
+
+ bool IsSupportedBitmapPixelFormat(PixelFormat format);
}
[Unstable]
diff --git a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
new file mode 100644
index 0000000000..acf1801e0a
--- /dev/null
+++ b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
@@ -0,0 +1,7 @@
+namespace Avalonia.Platform;
+
+public interface IReadableBitmapImpl
+{
+ PixelFormat? Format { get; }
+ ILockedFramebuffer Lock();
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
index fa1e1862b7..3284d34a0a 100644
--- a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
+++ b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
@@ -6,8 +6,7 @@ namespace Avalonia.Platform
/// Defines the platform-specific interface for a .
///
[Unstable]
- public interface IWriteableBitmapImpl : IBitmapImpl
+ public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl
{
- ILockedFramebuffer Lock();
}
}
diff --git a/src/Avalonia.Base/Platform/PixelFormat.cs b/src/Avalonia.Base/Platform/PixelFormat.cs
index 526303ebb1..99fe17055d 100644
--- a/src/Avalonia.Base/Platform/PixelFormat.cs
+++ b/src/Avalonia.Base/Platform/PixelFormat.cs
@@ -1,9 +1,74 @@
-namespace Avalonia.Platform
+using System;
+
+namespace Avalonia.Platform
{
- public enum PixelFormat
+ internal enum PixelFormatEnum
{
Rgb565,
Rgba8888,
- Bgra8888
+ Bgra8888,
+ BlackWhite,
+ Gray2,
+ Gray4,
+ Gray8,
+ Gray16,
+ Gray32Float,
+ Rgba64
+ }
+
+ public record struct PixelFormat
+ {
+ internal PixelFormatEnum FormatEnum;
+
+ public int BitsPerPixel
+ {
+ get
+ {
+ if (FormatEnum == PixelFormatEnum.BlackWhite)
+ return 1;
+ else if (FormatEnum == PixelFormatEnum.Gray2)
+ return 2;
+ else if (FormatEnum == PixelFormatEnum.Gray4)
+ return 4;
+ else if (FormatEnum == PixelFormatEnum.Gray8)
+ return 8;
+ else if (FormatEnum == PixelFormatEnum.Rgb565
+ || FormatEnum == PixelFormatEnum.Gray16)
+ return 16;
+ else if (FormatEnum == PixelFormatEnum.Rgba64)
+ return 64;
+
+ return 32;
+ }
+ }
+
+ internal bool HasAlpha => FormatEnum == PixelFormatEnum.Rgba8888
+ || FormatEnum == PixelFormatEnum.Bgra8888
+ || FormatEnum == PixelFormatEnum.Rgba64;
+
+ internal PixelFormat(PixelFormatEnum format)
+ {
+ FormatEnum = format;
+ }
+
+ public static PixelFormat Rgb565 => PixelFormats.Rgb565;
+ public static PixelFormat Rgba8888 => PixelFormats.Rgba8888;
+ public static PixelFormat Bgra8888 => PixelFormats.Bgra8888;
+
+ public override string ToString() => FormatEnum.ToString();
+ }
+
+ public static class PixelFormats
+ {
+ public static PixelFormat Rgb565 { get; } = new PixelFormat(PixelFormatEnum.Rgb565);
+ public static PixelFormat Rgba8888 { get; } = new PixelFormat(PixelFormatEnum.Rgba8888);
+ public static PixelFormat Rgba64 { get; } = new PixelFormat(PixelFormatEnum.Rgba64);
+ public static PixelFormat Bgra8888 { get; } = new PixelFormat(PixelFormatEnum.Bgra8888);
+ public static PixelFormat BlackWhite { get; } = new PixelFormat(PixelFormatEnum.BlackWhite);
+ public static PixelFormat Gray2 { get; } = new PixelFormat(PixelFormatEnum.Gray2);
+ public static PixelFormat Gray4 { get; } = new PixelFormat(PixelFormatEnum.Gray4);
+ public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
+ public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
+ public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
}
}
diff --git a/src/Avalonia.Base/Platform/SystemNavigationManager.cs b/src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs
similarity index 50%
rename from src/Avalonia.Base/Platform/SystemNavigationManager.cs
rename to src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs
index 6165b2bb77..080cd8083e 100644
--- a/src/Avalonia.Base/Platform/SystemNavigationManager.cs
+++ b/src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs
@@ -5,13 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Platform
{
[Unstable]
- public interface ITopLevelWithSystemNavigationManager
- {
- ISystemNavigationManager SystemNavigationManager { get; }
- }
-
- [Unstable]
- public interface ISystemNavigationManager
+ public interface ISystemNavigationManagerImpl
{
public event EventHandler? BackRequested;
}
diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs
index 0c789ff20f..86f7adf0d1 100644
--- a/src/Avalonia.Base/Point.cs
+++ b/src/Avalonia.Base/Point.cs
@@ -251,6 +251,12 @@ namespace Avalonia
/// The transform.
/// The transformed point.
public Point Transform(Matrix transform) => transform.Transform(this);
+
+ internal Point Transform(Matrix4x4 matrix)
+ {
+ var vec = Vector2.Transform(new Vector2((float)X, (float)Y), matrix);
+ return new Point(vec.X, vec.Y);
+ }
///
/// Returns a new point with the specified X coordinate.
diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs
index 831ab28adc..49dc087933 100644
--- a/src/Avalonia.Base/Rect.cs
+++ b/src/Avalonia.Base/Rect.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Numerics;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
@@ -441,6 +442,32 @@ namespace Avalonia
return new Rect(new Point(left, top), new Point(right, bottom));
}
+
+ internal Rect TransformToAABB(Matrix4x4 matrix)
+ {
+ ReadOnlySpan points = stackalloc Point[4]
+ {
+ TopLeft.Transform(matrix),
+ TopRight.Transform(matrix),
+ BottomRight.Transform(matrix),
+ BottomLeft.Transform(matrix)
+ };
+
+ var left = double.MaxValue;
+ var right = double.MinValue;
+ var top = double.MaxValue;
+ var bottom = double.MinValue;
+
+ foreach (var p in points)
+ {
+ if (p.X < left) left = p.X;
+ if (p.X > right) right = p.X;
+ if (p.Y < top) top = p.Y;
+ if (p.Y > bottom) bottom = p.Y;
+ }
+
+ return new Rect(new Point(left, top), new Point(right, bottom));
+ }
///
/// Translates the rectangle by an offset.
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index a2209db09f..668d650ffd 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Numerics;
-using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Collections.Pooled;
using Avalonia.Media;
-using Avalonia.Rendering.Composition.Drawing;
-using Avalonia.Rendering.Composition.Server;
-using Avalonia.Threading;
using Avalonia.VisualTree;
// Special license applies License.md
@@ -38,6 +35,9 @@ public class CompositingRenderer : IRendererWithCompositor
///
public bool RenderOnlyOnRenderThread { get; set; } = true;
+ ///
+ public RendererDiagnostics Diagnostics { get; }
+
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces)
{
_root = root;
@@ -46,20 +46,21 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget = compositor.CreateCompositionTarget(surfaces);
CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
_update = Update;
+ Diagnostics = new RendererDiagnostics();
+ Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
}
- ///
- public bool DrawFps
- {
- get => CompositionTarget.DrawFps;
- set => CompositionTarget.DrawFps = value;
- }
-
- ///
- public bool DrawDirtyRects
+ private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- get => CompositionTarget.DrawDirtyRects;
- set => CompositionTarget.DrawDirtyRects = value;
+ switch (e.PropertyName)
+ {
+ case nameof(RendererDiagnostics.DebugOverlays):
+ CompositionTarget.DebugOverlays = Diagnostics.DebugOverlays;
+ break;
+ case nameof(RendererDiagnostics.LastLayoutPassTiming):
+ CompositionTarget.LastLayoutPassTiming = Diagnostics.LastLayoutPassTiming;
+ break;
+ }
}
///
@@ -83,8 +84,16 @@ public class CompositingRenderer : IRendererWithCompositor
}
///
- public IEnumerable HitTest(Point p, Visual root, Func? filter)
+ public IEnumerable HitTest(Point p, Visual? root, Func? filter)
{
+ CompositionVisual? rootVisual = null;
+ if (root != null)
+ {
+ if (root.CompositionVisual == null)
+ yield break;
+ rootVisual = root.CompositionVisual;
+ }
+
Func? f = null;
if (filter != null)
f = v =>
@@ -93,8 +102,8 @@ public class CompositingRenderer : IRendererWithCompositor
return filter(dlv.Visual);
return true;
};
-
- var res = CompositionTarget.TryHitTest(p, f);
+
+ var res = CompositionTarget.TryHitTest(p, rootVisual, f);
if(res == null)
yield break;
foreach(var v in res)
@@ -257,6 +266,7 @@ public class CompositingRenderer : IRendererWithCompositor
_recalculateChildren.Clear();
CompositionTarget.Size = _root.ClientSize;
CompositionTarget.Scaling = _root.RenderScaling;
+ TriggerSceneInvalidatedOnBatchCompletion(_compositor.RequestCommitAsync());
}
private async void TriggerSceneInvalidatedOnBatchCompletion(Task batchCompletion)
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
index b019d1792b..f1905fec08 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
@@ -61,9 +61,6 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
return false;
if (custom != null)
{
- // Simulate the old behavior
- // TODO: Change behavior once legacy renderers are removed
- pt += new Point(Offset.X, Offset.Y);
return custom.HitTest(pt);
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
index 2f7e261250..ab4329df62 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Avalonia.Rendering.Composition.Server;
+using Avalonia.Threading;
namespace Avalonia.Rendering.Composition;
@@ -55,6 +56,6 @@ public class CompositionDrawingSurface : CompositionSurface
~CompositionDrawingSurface()
{
- Dispose();
+ Dispatcher.UIThread.Post(Dispose);
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
index 0c24d6cd44..f2fa846205 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
@@ -28,16 +28,15 @@ namespace Avalonia.Rendering.Composition
///
/// Attempts to perform a hit-tst
///
- ///
- ///
///
- public PooledList? TryHitTest(Point point, Func? filter)
+ public PooledList? TryHitTest(Point point, CompositionVisual? root, Func? filter)
{
Server.Readback.NextRead();
- if (Root == null)
+ root ??= Root;
+ if (root == null)
return null;
var res = new PooledList();
- HitTestCore(Root, point, res, filter);
+ HitTestCore(root, point, res, filter);
return res;
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
index 6dba18704f..801dd32d59 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
@@ -15,7 +14,7 @@ public partial class Compositor
///
public CompositionTarget CreateCompositionTarget(Func> surfaces)
{
- return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces));
+ return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces, DiagnosticTextRenderer));
}
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
index bdcbe65403..153b32c5f3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
@@ -35,9 +35,14 @@ namespace Avalonia.Rendering.Composition
private Task? _pendingBatch;
private readonly object _pendingBatchLock = new();
private List _pendingServerCompositorJobs = new();
+ private DiagnosticTextRenderer? _diagnosticTextRenderer;
internal IEasing DefaultEasing { get; }
-
+
+ private DiagnosticTextRenderer DiagnosticTextRenderer
+ => _diagnosticTextRenderer ??= new(Typeface.Default.GlyphTypeface, 12.0);
+
+ internal event Action? AfterCommit;
///
/// Creates a new compositor on a specified render loop that would use a particular GPU
@@ -88,6 +93,7 @@ namespace Avalonia.Rendering.Composition
{
if (_invokeBeforeCommitWrite.Count > 0)
RequestCommitAsync();
+ AfterCommit?.Invoke();
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs b/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
new file mode 100644
index 0000000000..b01fb46aa3
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
@@ -0,0 +1,78 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Server
+{
+ ///
+ /// A class used to render diagnostic strings (only!), with caching of ASCII glyph runs.
+ ///
+ internal sealed class DiagnosticTextRenderer
+ {
+ private const char FirstChar = (char)32;
+ private const char LastChar = (char)126;
+
+ private readonly GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
+
+ public double GetMaxHeight()
+ {
+ var maxHeight = 0.0;
+
+ for (var c = FirstChar; c <= LastChar; c++)
+ {
+ var height = _runs[c - FirstChar].Size.Height;
+ if (height > maxHeight)
+ {
+ maxHeight = height;
+ }
+ }
+
+ return maxHeight;
+ }
+
+ public DiagnosticTextRenderer(IGlyphTypeface typeface, double fontRenderingEmSize)
+ {
+ var chars = new char[LastChar - FirstChar + 1];
+ for (var c = FirstChar; c <= LastChar; c++)
+ {
+ var index = c - FirstChar;
+ chars[index] = c;
+ var glyph = typeface.GetGlyph(c);
+ _runs[index] = new GlyphRun(typeface, fontRenderingEmSize, chars.AsMemory(index, 1), new[] { glyph });
+ }
+ }
+
+ public Size MeasureAsciiText(ReadOnlySpan text)
+ {
+ var width = 0.0;
+ var height = 0.0;
+
+ foreach (var c in text)
+ {
+ var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
+ var run = _runs[effectiveChar - FirstChar];
+ width += run.Size.Width;
+ height = Math.Max(height, run.Size.Height);
+ }
+
+ return new Size(width, height);
+ }
+
+ public void DrawAsciiText(IDrawingContextImpl context, ReadOnlySpan text, IBrush foreground)
+ {
+ var offset = 0.0;
+ var originalTransform = context.Transform;
+
+ foreach (var c in text)
+ {
+ var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
+ var run = _runs[effectiveChar - FirstChar];
+ context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
+ context.DrawGlyphRun(foreground, run.PlatformImpl);
+ offset += run.Size.Width;
+ }
+
+ context.Transform = originalTransform;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
index ebab39cee8..03bd965fa8 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
@@ -1,11 +1,8 @@
using System;
using System.Diagnostics;
using System.Globalization;
-using System.Linq;
using Avalonia.Media;
-using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
-using Avalonia.Utilities;
// Special license applies License.md
@@ -17,26 +14,18 @@ namespace Avalonia.Rendering.Composition.Server;
internal class FpsCounter
{
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
+ private readonly DiagnosticTextRenderer _textRenderer;
+
private int _framesThisSecond;
private int _totalFrames;
private int _fps;
private TimeSpan _lastFpsUpdate;
- const int FirstChar = 32;
- const int LastChar = 126;
- // ASCII chars
- private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
-
- public FpsCounter(IGlyphTypeface typeface)
- {
- for (var c = FirstChar; c <= LastChar; c++)
- {
- var s = new string((char)c, 1);
- var glyph = typeface.GetGlyph((uint)(s[0]));
- _runs[c - FirstChar] = new GlyphRun(typeface, 18, s.AsMemory(), new ushort[] { glyph });
- }
- }
- public void FpsTick() => _framesThisSecond++;
+ public FpsCounter(DiagnosticTextRenderer textRenderer)
+ => _textRenderer = textRenderer;
+
+ public void FpsTick()
+ => _framesThisSecond++;
public void RenderFps(IDrawingContextImpl context, string aux)
{
@@ -53,27 +42,24 @@ internal class FpsCounter
_lastFpsUpdate = now;
}
- var fpsLine = FormattableString.Invariant($"Frame #{_totalFrames:00000000} FPS: {_fps:000} ") + aux;
- double width = 0;
- double height = 0;
- foreach (var ch in fpsLine)
- {
- var run = _runs[ch - FirstChar];
- width += run.Size.Width;
- height = Math.Max(height, run.Size.Height);
- }
+#if NET6_0_OR_GREATER
+ var fpsLine = string.Create(CultureInfo.InvariantCulture, $"Frame #{_totalFrames:00000000} FPS: {_fps:000} {aux}");
+#else
+ var fpsLine = FormattableString.Invariant($"Frame #{_totalFrames:00000000} FPS: {_fps:000} {aux}");
+#endif
- var rect = new Rect(0, 0, width + 3, height + 3);
+ var size = _textRenderer.MeasureAsciiText(fpsLine.AsSpan());
+ var rect = new Rect(0.0, 0.0, size.Width + 3.0, size.Height + 3.0);
context.DrawRectangle(Brushes.Black, null, rect);
- double offset = 0;
- foreach (var ch in fpsLine)
- {
- var run = _runs[ch - FirstChar];
- context.Transform = Matrix.CreateTranslation(offset, 0);
- context.DrawGlyphRun(Brushes.White, run.PlatformImpl);
- offset += run.Size.Width;
- }
+ _textRenderer.DrawAsciiText(context, fpsLine.AsSpan(), Brushes.White);
+ }
+
+ public void Reset()
+ {
+ _framesThisSecond = 0;
+ _totalFrames = 0;
+ _fps = 0;
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FrameTimeGraph.cs b/src/Avalonia.Base/Rendering/Composition/Server/FrameTimeGraph.cs
new file mode 100644
index 0000000000..d103b068a6
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/Composition/Server/FrameTimeGraph.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+///
+/// Represents a simple time graph for diagnostics purpose, used to show layout and render times.
+///
+internal sealed class FrameTimeGraph
+{
+ private const double HeaderPadding = 2.0;
+
+ private readonly IPlatformRenderInterface _renderInterface;
+ private readonly ImmutableSolidColorBrush _borderBrush;
+ private readonly ImmutablePen _graphPen;
+ private readonly double[] _frameValues;
+ private readonly Size _size;
+ private readonly Size _headerSize;
+ private readonly Size _graphSize;
+ private readonly double _defaultMaxY;
+ private readonly string _title;
+ private readonly DiagnosticTextRenderer _textRenderer;
+
+ private int _startFrameIndex;
+ private int _frameCount;
+
+ public Size Size
+ => _size;
+
+ public FrameTimeGraph(int maxFrames, Size size, double defaultMaxY, string title,
+ DiagnosticTextRenderer textRenderer)
+ {
+ Debug.Assert(maxFrames >= 1);
+ Debug.Assert(size.Width > 0.0);
+ Debug.Assert(size.Height > 0.0);
+
+ _renderInterface = AvaloniaLocator.Current.GetRequiredService();
+ _borderBrush = new ImmutableSolidColorBrush(0x80808080);
+ _graphPen = new ImmutablePen(Brushes.Blue);
+ _frameValues = new double[maxFrames];
+ _size = size;
+ _headerSize = new Size(size.Width, textRenderer.GetMaxHeight() + HeaderPadding * 2.0);
+ _graphSize = new Size(size.Width, size.Height - _headerSize.Height);
+ _defaultMaxY = defaultMaxY;
+ _title = title;
+ _textRenderer = textRenderer;
+ }
+
+ public void AddFrameValue(double value)
+ {
+ if (_frameCount < _frameValues.Length)
+ {
+ _frameValues[_startFrameIndex + _frameCount] = value;
+ ++_frameCount;
+ }
+ else
+ {
+ // overwrite oldest value
+ _frameValues[_startFrameIndex] = value;
+ if (++_startFrameIndex == _frameValues.Length)
+ {
+ _startFrameIndex = 0;
+ }
+ }
+ }
+
+ public void Reset()
+ {
+ _startFrameIndex = 0;
+ _frameCount = 0;
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ var originalTransform = context.Transform;
+ context.PushClip(new Rect(_size));
+
+ context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_size)));
+ context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_headerSize)));
+
+ context.Transform = originalTransform * Matrix.CreateTranslation(HeaderPadding, HeaderPadding);
+ _textRenderer.DrawAsciiText(context, _title.AsSpan(), Brushes.Black);
+
+ if (_frameCount > 0)
+ {
+ var (min, avg, max) = GetYValues();
+
+ DrawLabelledValue(context, "Min", min, originalTransform, _headerSize.Width * 0.19);
+ DrawLabelledValue(context, "Avg", avg, originalTransform, _headerSize.Width * 0.46);
+ DrawLabelledValue(context, "Max", max, originalTransform, _headerSize.Width * 0.73);
+
+ context.Transform = originalTransform * Matrix.CreateTranslation(0.0, _headerSize.Height);
+ context.DrawGeometry(null, _graphPen, BuildGraphGeometry(Math.Max(max, _defaultMaxY)));
+ }
+
+ context.Transform = originalTransform;
+ context.PopClip();
+ }
+
+ private void DrawLabelledValue(IDrawingContextImpl context, string label, double value, in Matrix originalTransform,
+ double left)
+ {
+ context.Transform = originalTransform * Matrix.CreateTranslation(left + HeaderPadding, HeaderPadding);
+
+ var brush = value <= _defaultMaxY ? Brushes.Black : Brushes.Red;
+
+#if NET6_0_OR_GREATER
+ Span buffer = stackalloc char[24];
+ buffer.TryWrite(CultureInfo.InvariantCulture, $"{label}: {value,5:F2}ms", out var charsWritten);
+ _textRenderer.DrawAsciiText(context, buffer.Slice(0, charsWritten), brush);
+#else
+ var text = FormattableString.Invariant($"{label}: {value,5:F2}ms");
+ _textRenderer.DrawAsciiText(context, text.AsSpan(), brush);
+#endif
+ }
+
+ private IStreamGeometryImpl BuildGraphGeometry(double maxY)
+ {
+ Debug.Assert(_frameCount > 0);
+
+ var graphGeometry = _renderInterface.CreateStreamGeometry();
+ using var geometryContext = graphGeometry.Open();
+
+ var xRatio = _graphSize.Width / _frameValues.Length;
+ var yRatio = _graphSize.Height / maxY;
+
+ geometryContext.BeginFigure(new Point(0.0, _graphSize.Height - GetFrameValue(0) * yRatio), false);
+
+ for (var i = 1; i < _frameCount; ++i)
+ {
+ var x = Math.Round(i * xRatio);
+ var y = _graphSize.Height - GetFrameValue(i) * yRatio;
+ geometryContext.LineTo(new Point(x, y));
+ }
+
+ geometryContext.EndFigure(false);
+ return graphGeometry;
+ }
+
+ private (double Min, double Average, double Max) GetYValues()
+ {
+ Debug.Assert(_frameCount > 0);
+
+ var min = double.MaxValue;
+ var max = double.MinValue;
+ var total = 0.0;
+
+ for (var i = 0; i < _frameCount; ++i)
+ {
+ var y = GetFrameValue(i);
+
+ total += y;
+
+ if (y < min)
+ {
+ min = y;
+ }
+
+ if (y > max)
+ {
+ max = y;
+ }
+ }
+
+ return (min, total / _frameCount, max);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private double GetFrameValue(int frameOffset)
+ => _frameValues[(_startFrameIndex + frameOffset) % _frameValues.Length];
+}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
index b172430fbb..63ec8d756b 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Numerics;
+using System.Diagnostics;
using System.Threading;
using Avalonia.Media;
using Avalonia.Media.Imaging;
@@ -21,11 +21,12 @@ namespace Avalonia.Rendering.Composition.Server
{
private readonly ServerCompositor _compositor;
private readonly Func> _surfaces;
+ private readonly DiagnosticTextRenderer _diagnosticTextRenderer;
private static long s_nextId = 1;
- public long Id { get; }
- public ulong Revision { get; private set; }
private IRenderTarget? _renderTarget;
- private FpsCounter _fpsCounter = new FpsCounter(Typeface.Default.GlyphTypeface);
+ private FpsCounter? _fpsCounter;
+ private FrameTimeGraph? _renderTimeGraph;
+ private FrameTimeGraph? _layoutTimeGraph;
private Rect _dirtyRect;
private Random _random = new();
private Size _layerSize;
@@ -35,18 +36,34 @@ namespace Avalonia.Rendering.Composition.Server
private HashSet _attachedVisuals = new();
private Queue _adornerUpdateQueue = new();
+ public long Id { get; }
+ public ulong Revision { get; private set; }
public ICompositionTargetDebugEvents? DebugEvents { get; set; }
public ReadbackIndices Readback { get; } = new();
public int RenderedVisuals { get; set; }
- public ServerCompositionTarget(ServerCompositor compositor, Func> surfaces) :
- base(compositor)
+ private FpsCounter FpsCounter
+ => _fpsCounter ??= new FpsCounter(_diagnosticTextRenderer);
+
+ private FrameTimeGraph LayoutTimeGraph
+ => _layoutTimeGraph ??= CreateTimeGraph("Layout");
+
+ private FrameTimeGraph RenderTimeGraph
+ => _renderTimeGraph ??= CreateTimeGraph("Render");
+
+ public ServerCompositionTarget(ServerCompositor compositor, Func> surfaces,
+ DiagnosticTextRenderer diagnosticTextRenderer)
+ : base(compositor)
{
_compositor = compositor;
_surfaces = surfaces;
+ _diagnosticTextRenderer = diagnosticTextRenderer;
Id = Interlocked.Increment(ref s_nextId);
}
+ private FrameTimeGraph CreateTimeGraph(string title)
+ => new(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, _diagnosticTextRenderer);
+
partial void OnIsEnabledChanged()
{
if (IsEnabled)
@@ -62,7 +79,33 @@ namespace Avalonia.Rendering.Composition.Server
v.Deactivate();
}
}
-
+
+ partial void OnDebugOverlaysChanged()
+ {
+ if ((DebugOverlays & RendererDebugOverlays.Fps) == 0)
+ {
+ _fpsCounter?.Reset();
+ }
+
+ if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) == 0)
+ {
+ _layoutTimeGraph?.Reset();
+ }
+
+ if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) == 0)
+ {
+ _renderTimeGraph?.Reset();
+ }
+ }
+
+ partial void OnLastLayoutPassTimingChanged()
+ {
+ if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
+ {
+ LayoutTimeGraph.AddFrameValue(LastLayoutPassTiming.Elapsed.TotalMilliseconds);
+ }
+ }
+
partial void DeserializeChangesExtra(BatchStreamReader c)
{
_redrawRequested = true;
@@ -92,7 +135,10 @@ namespace Avalonia.Rendering.Composition.Server
return;
Revision++;
-
+
+ var captureTiming = (DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0;
+ var startingTimestamp = captureTiming ? Stopwatch.GetTimestamp() : 0L;
+
// Update happens in a separate phase to extend dirty rect if needed
Root.Update(this);
@@ -137,33 +183,69 @@ namespace Avalonia.Rendering.Composition.Server
targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
new Rect(_layerSize),
new Rect(Size), BitmapInterpolationMode.LowQuality);
-
-
- if (DrawDirtyRects)
- {
- targetContext.DrawRectangle(new ImmutableSolidColorBrush(
- new Color(30, (byte)_random.Next(255), (byte)_random.Next(255),
- (byte)_random.Next(255)))
- , null, _dirtyRect);
- }
- if (DrawFps)
+ if (DebugOverlays != RendererDebugOverlays.None)
{
- var nativeMem = ByteSizeHelper.ToString((ulong)(
- (Compositor.BatchMemoryPool.CurrentUsage + Compositor.BatchMemoryPool.CurrentPool) *
- Compositor.BatchMemoryPool.BufferSize), false);
- var managedMem = ByteSizeHelper.ToString((ulong)(
- (Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
- Compositor.BatchObjectPool.ArraySize *
- IntPtr.Size), false);
- _fpsCounter.RenderFps(targetContext, FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"));
+ if (captureTiming)
+ {
+ var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
+ RenderTimeGraph.AddFrameValue(elapsed.TotalMilliseconds);
+ }
+
+ DrawOverlays(targetContext);
}
+
RenderedVisuals = 0;
_dirtyRect = default;
}
}
+ private void DrawOverlays(IDrawingContextImpl targetContext)
+ {
+ if ((DebugOverlays & RendererDebugOverlays.DirtyRects) != 0)
+ {
+ targetContext.DrawRectangle(
+ new ImmutableSolidColorBrush(
+ new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))),
+ null,
+ _dirtyRect);
+ }
+
+ if ((DebugOverlays & RendererDebugOverlays.Fps) != 0)
+ {
+ var nativeMem = ByteSizeHelper.ToString((ulong) (
+ (Compositor.BatchMemoryPool.CurrentUsage + Compositor.BatchMemoryPool.CurrentPool) *
+ Compositor.BatchMemoryPool.BufferSize), false);
+ var managedMem = ByteSizeHelper.ToString((ulong) (
+ (Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
+ Compositor.BatchObjectPool.ArraySize *
+ IntPtr.Size), false);
+ FpsCounter.RenderFps(targetContext,
+ FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"));
+ }
+
+ var top = 0.0;
+
+ void DrawTimeGraph(FrameTimeGraph graph)
+ {
+ top += 8.0;
+ targetContext.Transform = Matrix.CreateTranslation(Size.Width - graph.Size.Width - 8.0, top);
+ graph.Render(targetContext);
+ top += graph.Size.Height;
+ }
+
+ if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
+ {
+ DrawTimeGraph(LayoutTimeGraph);
+ }
+
+ if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0)
+ {
+ DrawTimeGraph(RenderTimeGraph);
+ }
+ }
+
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(rect, Scaling);
private static Rect SnapToDevicePixels(Rect rect, double scale)
diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs
deleted file mode 100644
index f0b993a2b0..0000000000
--- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs
+++ /dev/null
@@ -1,780 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Avalonia.Logging;
-using Avalonia.Media;
-using Avalonia.Media.Immutable;
-using Avalonia.Platform;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Threading;
-using Avalonia.Utilities;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering
-{
- ///
- /// A renderer which renders the state of the visual tree to an intermediate scene graph
- /// representation which is then rendered on a rendering thread.
- ///
- public class DeferredRenderer : RendererBase, IRenderer, IRenderLoopTask, IVisualBrushRenderer
- {
- private readonly IDispatcher? _dispatcher;
- private readonly IRenderLoop? _renderLoop;
- private readonly Func? _renderTargetFactory;
- private readonly PlatformRenderInterfaceContextManager? _renderInterface;
- private readonly Visual _root;
- private readonly ISceneBuilder _sceneBuilder;
-
- private bool _running;
- private bool _disposed;
- private volatile IRef? _scene;
- private DirtyVisuals? _dirty;
- private HashSet? _recalculateChildren;
- private IRef? _overlay;
- private int _lastSceneId = -1;
- private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
- private IRef? _currentDraw;
- private readonly IDeferredRendererLock _lock;
- private readonly object _sceneLock = new object();
- private readonly object _startStopLock = new object();
- private readonly object _renderLoopIsRenderingLock = new object();
- private readonly Action _updateSceneIfNeededDelegate;
- private List? _pendingRenderThreadJobs;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The control to render.
- /// The render loop.
- /// The target render factory.
- /// The Platform Render Context.
- /// The scene builder to use. Optional.
- /// The dispatcher to use. Optional.
- /// Lock object used before trying to access render target
- public DeferredRenderer(
- IRenderRoot root,
- IRenderLoop renderLoop,
- Func renderTargetFactory,
- PlatformRenderInterfaceContextManager? renderInterface = null,
- ISceneBuilder? sceneBuilder = null,
- IDispatcher? dispatcher = null,
- IDeferredRendererLock? rendererLock = null) : base(true)
- {
- _dispatcher = dispatcher ?? Dispatcher.UIThread;
- _root = root as Visual ?? throw new ArgumentNullException(nameof(root));
- _sceneBuilder = sceneBuilder ?? new SceneBuilder();
- Layers = new RenderLayers();
- _renderLoop = renderLoop;
- _renderTargetFactory = renderTargetFactory;
- _renderInterface = renderInterface;
- _lock = rendererLock ?? new ManagedDeferredRendererLock();
- _updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The control to render.
- /// The render target.
- /// The scene builder to use. Optional.
- ///
- /// This constructor is intended to be used for unit testing.
- ///
- public DeferredRenderer(
- Visual root,
- IRenderTarget renderTarget,
- ISceneBuilder? sceneBuilder = null) : base(true)
- {
- _root = root ?? throw new ArgumentNullException(nameof(root));
- RenderTarget = renderTarget ?? throw new ArgumentNullException(nameof(renderTarget));
- _sceneBuilder = sceneBuilder ?? new SceneBuilder();
- Layers = new RenderLayers();
- _lock = new ManagedDeferredRendererLock();
- _updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
- }
-
- ///
- public bool DrawFps { get; set; }
-
- ///
- public bool DrawDirtyRects { get; set; }
-
- ///
- /// Gets or sets a path to which rendered frame should be rendered for debugging.
- ///
- public string? DebugFramesPath { get; set; }
-
- ///
- /// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered
- ///
- public bool RenderOnlyOnRenderThread { get; set; }
-
- ///
- public event EventHandler? SceneInvalidated;
-
- ///
- /// Gets the render layers.
- ///
- internal RenderLayers Layers { get; }
-
- ///
- /// Gets the current render target.
- ///
- internal IRenderTarget? RenderTarget { get; private set; }
-
- ///
- public void AddDirty(Visual visual)
- {
- _dirty?.Add(visual);
- }
-
- ///
- /// Disposes of the renderer and detaches from the render loop.
- ///
- public void Dispose()
- {
- lock (_sceneLock)
- {
- if (_disposed)
- return;
- _disposed = true;
- var scene = _scene;
- _scene = null;
- scene?.Dispose();
- }
-
- Stop();
- // Wait for any in-progress rendering to complete
- lock(_renderLoopIsRenderingLock){}
- DisposeRenderTarget();
- }
-
- public void RecalculateChildren(Visual visual) => _recalculateChildren?.Add(visual);
-
- void DisposeRenderTarget()
- {
- using (var l = _lock.TryLock())
- {
- if(l == null)
- {
- // We are still trying to render on the render thread, try again a bit later
- DispatcherTimer.RunOnce(DisposeRenderTarget, TimeSpan.FromMilliseconds(50),
- DispatcherPriority.Background);
- return;
- }
-
- Layers.Clear();
- RenderTarget?.Dispose();
- RenderTarget = null;
- }
- }
-
- ///
- public IEnumerable HitTest(Point p, Visual root, Func? filter)
- {
- EnsureCanHitTest();
-
- //It's safe to access _scene here without a lock since
- //it's only changed from UI thread which we are currently on
- return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty();
- }
-
- ///
- public Visual? HitTestFirst(Point p, Visual root, Func? filter)
- {
- EnsureCanHitTest();
-
- //It's safe to access _scene here without a lock since
- //it's only changed from UI thread which we are currently on
- return _scene?.Item.HitTestFirst(p, root, filter);
- }
-
- ///
- public void Paint(Rect rect)
- {
- if (RenderOnlyOnRenderThread)
- {
- // Renderer is stopped and doesn't tick on the render thread
- // This indicates a bug somewhere in our code
- // (currently happens when a window gets minimized with Show desktop on Windows 10)
- if(!_running)
- return;
-
- while (true)
- {
- Scene? scene;
- bool? updated;
- lock (_sceneLock)
- {
- updated = UpdateScene();
- scene = _scene?.Item;
- }
-
- // Renderer is in invalid state, skip drawing
- if(updated == null)
- return;
-
- // Wait for the scene to be rendered or disposed
- scene?.Rendered.Wait();
-
- // That was an up-to-date scene, we can return immediately
- if (updated == true)
- return;
- }
- }
- else
- {
- var t = (IRenderLoopTask)this;
- if (t.NeedsUpdate)
- UpdateScene();
- if (_scene?.Item != null)
- Render(true);
- }
- }
-
- ///
- public void Resized(Size size)
- {
- }
-
- ///
- public void Start()
- {
- lock (_startStopLock)
- {
- if (!_running && _renderLoop != null)
- {
- _renderLoop.Add(this);
- _running = true;
- }
- }
- }
-
- ///
- public void Stop()
- {
- lock (_startStopLock)
- {
- if (_running && _renderLoop != null)
- {
- _renderLoop.Remove(this);
- _running = false;
- }
- }
- }
-
- public ValueTask
double RenderScaling { get; }
- ///
- /// Adds a rectangle to the window's dirty region.
- ///
- /// The rectangle.
- void Invalidate(Rect rect);
-
///
/// Converts a point from screen to client coordinates.
///
diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs
index f3f5b5e99b..ba960ff5f3 100644
--- a/src/Avalonia.Base/Rendering/IRenderer.cs
+++ b/src/Avalonia.Base/Rendering/IRenderer.cs
@@ -1,5 +1,4 @@
using System;
-using Avalonia.VisualTree;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Rendering.Composition;
@@ -12,15 +11,9 @@ namespace Avalonia.Rendering
public interface IRenderer : IDisposable
{
///
- /// Gets or sets a value indicating whether the renderer should draw an FPS counter.
+ /// Gets a value indicating whether the renderer should draw specific diagnostics.
///
- bool DrawFps { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the renderer should draw a visual representation
- /// of its dirty rectangles.
- ///
- bool DrawDirtyRects { get; set; }
+ RendererDiagnostics Diagnostics { get; }
///
/// Raised when a portion of the scene has been invalidated.
diff --git a/src/Avalonia.Base/Rendering/IRendererFactory.cs b/src/Avalonia.Base/Rendering/IRendererFactory.cs
deleted file mode 100644
index 11d89ef94c..0000000000
--- a/src/Avalonia.Base/Rendering/IRendererFactory.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-
-namespace Avalonia.Rendering
-{
- ///
- /// Defines the interface for a renderer factory.
- ///
- public interface IRendererFactory
- {
- ///
- /// Creates a renderer.
- ///
- /// The root visual.
- /// The render loop.
- IRenderer Create(IRenderRoot root, IRenderLoop renderLoop);
- }
-}
diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
index 60b144e806..c67ac7057d 100644
--- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
@@ -10,102 +10,12 @@ using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
///
- /// A renderer which renders the state of the visual tree without an intermediate scene graph
- /// representation.
+ /// This class is used to render the visual tree into a DrawingContext by doing
+ /// a simple tree traversal.
+ /// It's currently used mostly for RenderTargetBitmap.Render and VisualBrush
///
- ///
- /// The immediate renderer supports only clip-bound-based hit testing; a control's geometry is
- /// not taken into account.
- ///
- public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer
+ internal class ImmediateRenderer : IVisualBrushRenderer//, IRenderer
{
- private readonly Visual _root;
- private readonly Func _renderTargetFactory;
- private readonly PlatformRenderInterfaceContextManager? _renderContext;
- private readonly IRenderRoot? _renderRoot;
- private bool _updateTransformedBounds = true;
- private IRenderTarget? _renderTarget;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The control to render.
- /// The target render factory.
- /// The render contex.
- public ImmediateRenderer(Visual root, Func renderTargetFactory,
- PlatformRenderInterfaceContextManager? renderContext = null)
- {
- _root = root ?? throw new ArgumentNullException(nameof(root));
- _renderTargetFactory = renderTargetFactory;
- _renderContext = renderContext;
- _renderRoot = root as IRenderRoot;
- }
-
- private ImmediateRenderer(Visual root, Func renderTargetFactory, bool updateTransformedBounds)
- {
- _root = root ?? throw new ArgumentNullException(nameof(root));
- _renderTargetFactory = renderTargetFactory;
- _renderRoot = root as IRenderRoot;
- _updateTransformedBounds = updateTransformedBounds;
- }
-
- ///
- public bool DrawFps { get; set; }
-
- ///
- public bool DrawDirtyRects { get; set; }
-
- ///
- public event EventHandler? SceneInvalidated;
-
- ///
- public void Paint(Rect rect)
- {
- if (_renderTarget == null)
- {
- _renderTarget = _renderTargetFactory();
- }
-
- try
- {
- using (var context = new DrawingContext(_renderTarget.CreateDrawingContext(this)))
- {
- context.PlatformImpl.Clear(Colors.Transparent);
-
- using (context.PushTransformContainer())
- {
- Render(context, _root, _root.Bounds);
- }
-
- if (DrawDirtyRects)
- {
- var color = (uint)new Random().Next(0xffffff) | 0x44000000;
- context.FillRectangle(
- new SolidColorBrush(color),
- rect);
- }
-
- if (DrawFps)
- {
- RenderFps(context, _root.Bounds, null);
- }
- }
- }
- catch (RenderTargetCorruptedException ex)
- {
- Logger.TryGet(LogEventLevel.Information, LogArea.Animations)?.Log(this, "Render target was corrupted. Exception: {0}", ex);
- _renderTarget.Dispose();
- _renderTarget = null;
- }
-
- SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
- }
-
- ///
- public void Resized(Size size)
- {
- }
-
///
/// Renders a visual to a render target.
///
@@ -113,11 +23,8 @@ namespace Avalonia.Rendering
/// The render target.
public static void Render(Visual visual, IRenderTarget target)
{
- using (var renderer = new ImmediateRenderer(visual, () => target, updateTransformedBounds: false))
- using (var context = new DrawingContext(target.CreateDrawingContext(renderer)))
- {
- renderer.Render(context, visual, visual.Bounds);
- }
+ using var context = new DrawingContext(target.CreateDrawingContext(new ImmediateRenderer()));
+ Render(context, visual, visual.Bounds);
}
///
@@ -127,77 +34,9 @@ namespace Avalonia.Rendering
/// The drawing context.
public static void Render(Visual visual, DrawingContext context)
{
- using (var renderer = new ImmediateRenderer(visual,
- () => throw new InvalidOperationException("This is not supposed to be called"),
- updateTransformedBounds: false))
- {
- renderer.Render(context, visual, visual.Bounds);
- }
+ Render(context, visual, visual.Bounds);
}
-
- ///
- public void AddDirty(Visual visual)
- {
- if (!visual.Bounds.IsDefault)
- {
- var m = visual.TransformToVisual(_root);
-
- if (m.HasValue)
- {
- var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value);
-
- //use transformedbounds as previous render state of the visual bounds
- //so we can invalidate old and new bounds of a control in case it moved/shrinked
- if (visual.TransformedBounds.HasValue)
- {
- var trb = visual.TransformedBounds.Value;
- var trBounds = trb.Bounds.TransformToAABB(trb.Transform);
-
- if (trBounds != bounds)
- {
- _renderRoot?.Invalidate(trBounds);
- }
- }
-
- _renderRoot?.Invalidate(bounds);
- }
- }
- }
-
- ///
- /// Ends the operation of the renderer.
- ///
- public void Dispose()
- {
- _renderTarget?.Dispose();
- }
-
- ///
- public IEnumerable HitTest(Point p, Visual root, Func filter)
- {
- return HitTest(root, p, filter);
- }
-
- public Visual? HitTestFirst(Point p, Visual root, Func filter)
- {
- return HitTest(root, p, filter).FirstOrDefault();
- }
-
- ///
- public void RecalculateChildren(Visual visual) => AddDirty(visual);
-
- ///
- public void Start()
- {
- }
-
- ///
- public void Stop()
- {
- }
-
- public ValueTask TryGetRenderInterfaceFeature(Type featureType) =>
- new(_renderContext?.Value?.TryGetFeature(featureType));
+
///
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
@@ -215,18 +54,7 @@ namespace Avalonia.Rendering
internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)
{
- using var renderer = new ImmediateRenderer(visual,
- () => throw new InvalidOperationException("This is not supposed to be called"),
- updateTransformedBounds);
- renderer.Render(context, visual, visual.Bounds);
- }
-
- private static void ClearTransformedBounds(Visual visual)
- {
- foreach (var e in visual.GetSelfAndVisualDescendants())
- {
- visual.SetTransformedBounds(null);
- }
+ Render(context, visual, visual.Bounds);
}
private static Rect GetTransformedBounds(Visual visual)
@@ -244,45 +72,8 @@ namespace Avalonia.Rendering
}
}
- private static IEnumerable HitTest(
- Visual visual,
- Point p,
- Func? filter)
- {
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
-
- if (filter?.Invoke(visual) != false)
- {
- bool containsPoint;
-
- if (visual is ICustomSimpleHitTest custom)
- {
- containsPoint = custom.HitTest(p);
- }
- else
- {
- containsPoint = visual.TransformedBounds?.Contains(p) == true;
- }
-
- if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0)
- {
- foreach (var child in visual.VisualChildren.SortByZIndex())
- {
- foreach (var result in HitTest(child, p, filter))
- {
- yield return result;
- }
- }
- }
- if (containsPoint)
- {
- yield return visual;
- }
- }
- }
-
- private void Render(DrawingContext context, Visual visual, Rect clipRect)
+ private static void Render(DrawingContext context, Visual visual, Rect clipRect)
{
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
@@ -338,15 +129,7 @@ namespace Avalonia.Rendering
using (context.PushTransformContainer())
{
visual.Render(context);
-
-#pragma warning disable 0618
- var transformed =
- new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
-#pragma warning restore 0618
-
- if (_updateTransformedBounds)
- visual.SetTransformedBounds(transformed);
-
+
var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
: (IEnumerable)visual.VisualChildren;
@@ -362,18 +145,9 @@ namespace Avalonia.Rendering
: clipRect;
Render(context, child, childClipRect);
}
- else if (_updateTransformedBounds)
- {
- ClearTransformedBounds(child);
- }
}
}
}
-
- if (!visual.IsVisible && _updateTransformedBounds)
- {
- ClearTransformedBounds(visual);
- }
}
}
}
diff --git a/src/Avalonia.Base/Rendering/LayoutPassTiming.cs b/src/Avalonia.Base/Rendering/LayoutPassTiming.cs
new file mode 100644
index 0000000000..b4b6d1d4f1
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/LayoutPassTiming.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Avalonia.Rendering
+{
+ ///
+ /// Represents a single layout pass timing.
+ ///
+ /// The number of the layout pass.
+ /// The elapsed time during the layout pass.
+ internal readonly record struct LayoutPassTiming(int PassCounter, TimeSpan Elapsed);
+}
diff --git a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs
index c13bcfda96..82dcd8f184 100644
--- a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs
+++ b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs
@@ -6,9 +6,7 @@ using Avalonia.Reactive;
namespace Avalonia.Rendering;
-[Unstable]
-// TODO: Make it internal once legacy renderers are removed
-public class PlatformRenderInterfaceContextManager
+internal class PlatformRenderInterfaceContextManager
{
private readonly IPlatformGraphics? _graphics;
private IPlatformRenderInterfaceContext? _backend;
diff --git a/src/Avalonia.Base/Rendering/RenderLayer.cs b/src/Avalonia.Base/Rendering/RenderLayer.cs
deleted file mode 100644
index d1e3fcafb1..0000000000
--- a/src/Avalonia.Base/Rendering/RenderLayer.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Utilities;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering
-{
- public class RenderLayer
- {
- public RenderLayer(
- IDrawingContextImpl drawingContext,
- Size size,
- double scaling,
- Visual layerRoot)
- {
- Bitmap = RefCountable.Create(drawingContext.CreateLayer(size));
- Size = size;
- Scaling = scaling;
- LayerRoot = layerRoot;
- IsEmpty = true;
- }
-
- public IRef Bitmap { get; private set; }
- public bool IsEmpty { get; set; }
- public double Scaling { get; private set; }
- public Size Size { get; private set; }
- public Visual LayerRoot { get; }
-
- public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling)
- {
- if (Size != size || Scaling != scaling)
- {
- var resized = RefCountable.Create(drawingContext.CreateLayer(size));
-
- using (var context = resized.Item.CreateDrawingContext(null))
- {
- Bitmap.Dispose();
- context.Clear(default);
-
- Bitmap = resized;
- Scaling = scaling;
- Size = size;
- IsEmpty = true;
- }
- }
- }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/RenderLayers.cs b/src/Avalonia.Base/Rendering/RenderLayers.cs
deleted file mode 100644
index eff81e6bbf..0000000000
--- a/src/Avalonia.Base/Rendering/RenderLayers.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using Avalonia.Platform;
-using Avalonia.Rendering.SceneGraph;
-
-namespace Avalonia.Rendering
-{
- public class RenderLayers : IEnumerable
- {
- private readonly List _inner = new List();
- private readonly Dictionary _index = new Dictionary();
-
- public int Count => _inner.Count;
- public RenderLayer this[Visual layerRoot] => _index[layerRoot];
-
- public void Update(Scene scene, IDrawingContextImpl context)
- {
- for (var i = scene.Layers.Count - 1; i >= 0; --i)
- {
- var src = scene.Layers[i];
-
- if (!_index.TryGetValue(src.LayerRoot, out var layer))
- {
- layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot);
- _inner.Add(layer);
- _index.Add(src.LayerRoot, layer);
- }
- else
- {
- layer.RecreateBitmap(context, scene.Size, scene.Scaling);
- }
- }
-
- for (var i = 0; i < _inner.Count;)
- {
- var layer = _inner[i];
-
- if (!scene.Layers.Exists(layer.LayerRoot))
- {
- layer.Bitmap.Dispose();
- _inner.RemoveAt(i);
- _index.Remove(layer.LayerRoot);
- }
- else
- i++;
- }
- }
-
- public void Clear()
- {
- foreach (var layer in _index.Values)
- {
- layer.Bitmap.Dispose();
- }
-
- _index.Clear();
- _inner.Clear();
- }
-
- public bool TryGetValue(Visual layerRoot, [NotNullWhen(true)] out RenderLayer? value)
- {
- return _index.TryGetValue(layerRoot, out value);
- }
-
- public IEnumerator GetEnumerator() => _inner.GetEnumerator();
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- }
-}
diff --git a/src/Avalonia.Base/Rendering/RendererDebugOverlays.cs b/src/Avalonia.Base/Rendering/RendererDebugOverlays.cs
new file mode 100644
index 0000000000..85932f1568
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/RendererDebugOverlays.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace Avalonia.Rendering;
+
+///
+/// Represents the various types of overlays that can be drawn by a renderer.
+///
+[Flags]
+public enum RendererDebugOverlays
+{
+ ///
+ /// Do not draw any overlay.
+ ///
+ None = 0,
+
+ ///
+ /// Draw a FPS counter.
+ ///
+ Fps = 1 << 0,
+
+ ///
+ /// Draw invalidated rectangles each frame.
+ ///
+ DirtyRects = 1 << 1,
+
+ ///
+ /// Draw a graph of past layout times.
+ ///
+ LayoutTimeGraph = 1 << 2,
+
+ ///
+ /// Draw a graph of past render times.
+ ///
+ RenderTimeGraph = 1 << 3
+}
diff --git a/src/Avalonia.Base/Rendering/RendererDiagnostics.cs b/src/Avalonia.Base/Rendering/RendererDiagnostics.cs
new file mode 100644
index 0000000000..0897cac62e
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/RendererDiagnostics.cs
@@ -0,0 +1,57 @@
+using System.ComponentModel;
+
+namespace Avalonia.Rendering
+{
+ ///
+ /// Manages configurable diagnostics that can be displayed by a renderer.
+ ///
+ public class RendererDiagnostics : INotifyPropertyChanged
+ {
+ private RendererDebugOverlays _debugOverlays;
+ private LayoutPassTiming _lastLayoutPassTiming;
+ private PropertyChangedEventArgs? _debugOverlaysChangedEventArgs;
+ private PropertyChangedEventArgs? _lastLayoutPassTimingChangedEventArgs;
+
+ ///
+ /// Gets or sets which debug overlays are displayed by the renderer.
+ ///
+ public RendererDebugOverlays DebugOverlays
+ {
+ get => _debugOverlays;
+ set
+ {
+ if (_debugOverlays != value)
+ {
+ _debugOverlays = value;
+ OnPropertyChanged(_debugOverlaysChangedEventArgs ??= new(nameof(DebugOverlays)));
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the last layout pass timing that the renderer may display.
+ ///
+ internal LayoutPassTiming LastLayoutPassTiming
+ {
+ get => _lastLayoutPassTiming;
+ set
+ {
+ if (!_lastLayoutPassTiming.Equals(value))
+ {
+ _lastLayoutPassTiming = value;
+ OnPropertyChanged(_lastLayoutPassTimingChangedEventArgs ??= new(nameof(LastLayoutPassTiming)));
+ }
+ }
+ }
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// Called when a property changes on the object.
+ ///
+ /// The property change details.
+ protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
+ => PropertyChanged?.Invoke(this, args);
+ }
+}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
deleted file mode 100644
index b1d8301557..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ /dev/null
@@ -1,482 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Utilities;
-using Avalonia.Media.Imaging;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// A drawing context which builds a scene graph.
- ///
- internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
- {
- private readonly ISceneBuilder _sceneBuilder;
- private VisualNode? _node;
- private int _childIndex;
- private int _drawOperationindex;
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// A scene builder used for constructing child scenes for visual brushes.
- ///
- /// The scene layers.
- public DeferredDrawingContextImpl(ISceneBuilder sceneBuilder, SceneLayers layers)
- {
- _sceneBuilder = sceneBuilder;
- Layers = layers;
- }
-
- ///
- public Matrix Transform { get; set; } = Matrix.Identity;
-
- ///
- /// Gets the layers in the scene being built.
- ///
- public SceneLayers Layers { get; }
-
- ///
- /// Informs the drawing context of the visual node that is about to be rendered.
- ///
- /// The visual node.
- ///
- /// An object which when disposed will commit the changes to visual node.
- ///
- public UpdateState BeginUpdate(VisualNode node)
- {
- _ = node ?? throw new ArgumentNullException(nameof(node));
-
- if (_node != null)
- {
- if (_childIndex < _node.Children.Count)
- {
- _node.ReplaceChild(_childIndex, node);
- }
- else
- {
- _node.AddChild(node);
- }
-
- ++_childIndex;
- }
-
- var state = new UpdateState(this, _node, _childIndex, _drawOperationindex);
- _node = node;
- _childIndex = _drawOperationindex = 0;
- return state;
- }
-
- ///
- public void Clear(Color color)
- {
- // Cannot clear a deferred scene.
- }
-
- ///
- public void Dispose()
- {
- // Nothing to do here since we allocate no unmanaged resources.
- }
-
- ///
- /// Removes any remaining drawing operations from the visual node.
- ///
- ///
- /// Drawing operations are updated in place, overwriting existing drawing operations if
- /// they are different. Once drawing has completed for the current visual node, it is
- /// possible that there are stale drawing operations at the end of the list. This method
- /// trims these stale drawing operations.
- ///
- public void TrimChildren()
- {
- _node!.TrimChildren(_childIndex);
- }
-
- ///
- public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
- {
- Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode))
- {
- Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
- {
- // This method is currently only used to composite layers so shouldn't be called here.
- throw new NotSupportedException();
- }
-
- ///
- public void DrawLine(IPen pen, Point p1, Point p2)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
- {
- Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect,
- BoxShadows boxShadows = default)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows))
- {
- Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush)));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, material, rect))
- {
- Add(new ExperimentalAcrylicNode(Transform, material, rect));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, brush, pen, rect))
- {
- Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush)));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- public void Custom(ICustomDrawOperation custom)
- {
- var next = NextDrawAs();
- if (next == null || !next.Item.Equals(Transform, custom))
- Add(new CustomDrawOperation(custom, Transform));
- else
- ++_drawOperationindex;
- }
-
- public object? GetFeature(Type t) => null;
-
- ///
- public void DrawGlyphRun(IBrush foreground, IRef glyphRun)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))
- {
- Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground)));
- }
-
- else
- {
- ++_drawOperationindex;
- }
- }
- public IDrawingContextLayerImpl CreateLayer(Size size)
- {
- throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
- }
-
- ///
- public void PopClip()
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(null))
- {
- Add(new ClipNode());
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PopGeometryClip()
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(null))
- {
- Add(new GeometryClipNode());
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PopBitmapBlendMode()
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(null))
- {
- Add(new BitmapBlendModeNode());
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PopOpacity()
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(null))
- {
- Add(new OpacityNode());
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PopOpacityMask()
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(null, null))
- {
- Add(new OpacityMaskNode());
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PushClip(Rect clip)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, clip))
- {
- Add(new ClipNode(Transform, clip));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PushClip(RoundedRect clip)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, clip))
- {
- Add(new ClipNode(Transform, clip));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PushGeometryClip(IGeometryImpl? clip)
- {
- if (clip is null)
- return;
-
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(Transform, clip))
- {
- Add(new GeometryClipNode(Transform, clip));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PushOpacity(double opacity)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(opacity))
- {
- Add(new OpacityNode(opacity));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PushOpacityMask(IBrush mask, Rect bounds)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(mask, bounds))
- {
- Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- ///
- public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
- {
- var next = NextDrawAs();
-
- if (next == null || !next.Item.Equals(blendingMode))
- {
- Add(new BitmapBlendModeNode(blendingMode));
- }
- else
- {
- ++_drawOperationindex;
- }
- }
-
- public readonly struct UpdateState : IDisposable
- {
- public UpdateState(
- DeferredDrawingContextImpl owner,
- VisualNode? node,
- int childIndex,
- int drawOperationIndex)
- {
- Owner = owner;
- Node = node;
- ChildIndex = childIndex;
- DrawOperationIndex = drawOperationIndex;
- }
-
- public void Dispose()
- {
- Owner._node!.TrimDrawOperations(Owner._drawOperationindex);
-
- var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot!).Dirty;
-
- var drawOperations = Owner._node.DrawOperations;
- var drawOperationsCount = drawOperations.Count;
-
- for (var i = 0; i < drawOperationsCount; i++)
- {
- dirty.Add(drawOperations[i].Item.Bounds);
- }
-
- Owner._node = Node;
- Owner._childIndex = ChildIndex;
- Owner._drawOperationindex = DrawOperationIndex;
- }
-
- public DeferredDrawingContextImpl Owner { get; }
- public VisualNode? Node { get; }
- public int ChildIndex { get; }
- public int DrawOperationIndex { get; }
- }
-
- private void Add(T node) where T : class, IDrawOperation
- {
- using (var refCounted = RefCountable.Create(node))
- {
- Add(refCounted);
- }
- }
-
- private void Add(IRef node)
- {
- if (_drawOperationindex < _node!.DrawOperations.Count)
- {
- _node.ReplaceDrawOperation(_drawOperationindex, node);
- }
- else
- {
- _node.AddDrawOperation(node);
- }
-
- ++_drawOperationindex;
- }
-
- private IRef? NextDrawAs() where T : class, IDrawOperation
- {
- return _drawOperationindex < _node!.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef : null;
- }
-
- private IDisposable? CreateChildScene(IBrush? brush)
- {
- var visualBrush = brush as VisualBrush;
-
- if (visualBrush != null)
- {
- var visual = visualBrush.Visual;
-
- if (visual != null)
- {
- (visual as IVisualBrushInitialize)?.EnsureInitialized();
- var scene = new Scene(visual);
- _sceneBuilder.UpdateAll(scene);
- return scene;
- }
- }
-
- return null;
- }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs
deleted file mode 100644
index f469fdbfe8..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// Builds a scene graph from a visual tree.
- ///
- public interface ISceneBuilder
- {
- ///
- /// Builds the initial scene graph for a visual tree.
- ///
- /// The scene to build.
- void UpdateAll(Scene scene);
-
- ///
- /// Updates the visual (and potentially its children) in a scene.
- ///
- /// The scene.
- /// The visual to update.
- /// True if changes were made, otherwise false.
- bool Update(Scene scene, Visual visual);
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs
deleted file mode 100644
index 59a032748d..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Platform;
-using Avalonia.Utilities;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// Represents a node in the low-level scene graph representing a .
- ///
- public interface IVisualNode : IDisposable
- {
- ///
- /// Gets the visual to which the node relates.
- ///
- Visual Visual { get; }
-
- ///
- /// Gets the parent scene graph node.
- ///
- IVisualNode? Parent { get; }
-
- ///
- /// Gets the transform for the node from global to control coordinates.
- ///
- Matrix Transform { get; }
-
- ///
- /// Gets the corner radius of visual. Contents are clipped to this radius.
- ///
- CornerRadius ClipToBoundsRadius { get; }
-
- ///
- /// Gets the bounds of the node's geometry in global coordinates.
- ///
- Rect Bounds { get; }
-
- ///
- /// Gets the clip bounds for the node in global coordinates.
- ///
- Rect ClipBounds { get; }
-
- ///
- /// Gets the layout bounds for the node in global coordinates.
- ///
- Rect LayoutBounds { get; }
-
- ///
- /// Whether the node is clipped to .
- ///
- bool ClipToBounds { get; }
-
- ///
- /// Gets the node's clip geometry, if any.
- ///
- IGeometryImpl? GeometryClip { get; set; }
-
- ///
- /// Gets a value indicating whether one of the node's ancestors has a geometry clip.
- ///
- bool HasAncestorGeometryClip { get; }
-
- ///
- /// Gets the child scene graph nodes.
- ///
- IReadOnlyList Children { get; }
-
- ///
- /// Gets the drawing operations for the visual.
- ///
- IReadOnlyList> DrawOperations { get; }
-
- ///
- /// Gets the opacity of the scene graph node.
- ///
- double Opacity { get; }
-
- ///
- /// Sets up the drawing context for rendering the node's geometry.
- ///
- /// The drawing context.
- /// Whether to skip pushing the control's opacity.
- void BeginRender(IDrawingContextImpl context, bool skipOpacity);
-
- ///
- /// Resets the drawing context after rendering the node's geometry.
- ///
- /// The drawing context.
- /// Whether to skip popping the control's opacity.
- void EndRender(IDrawingContextImpl context, bool skipOpacity);
-
- ///
- /// Hit test the geometry in this node.
- ///
- /// The point in global coordinates.
- /// True if the point hits the node's geometry; otherwise false.
- ///
- /// This method does not recurse to child s, if you want
- /// to hit test children they must be hit tested manually.
- ///
- bool HitTest(Point p);
-
- bool Disposed { get; }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs
deleted file mode 100644
index 735eb3bb3f..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs
+++ /dev/null
@@ -1,352 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Avalonia.Collections.Pooled;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// Represents a scene graph used by the .
- ///
- public class Scene : IDisposable
- {
- private readonly Dictionary _index;
- private readonly TaskCompletionSource _rendered = new TaskCompletionSource();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The root visual to draw.
- public Scene(Visual rootVisual)
- : this(
- new VisualNode(rootVisual, null),
- new Dictionary(),
- new SceneLayers(rootVisual),
- 0)
- {
- _index.Add(rootVisual, Root);
- }
-
- private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation)
- {
- _ = root ?? throw new ArgumentNullException(nameof(root));
-
- var renderRoot = root.Visual as IRenderRoot;
-
- _index = index;
- Root = root;
- Layers = layers;
- Generation = generation;
- root.LayerRoot = root.Visual;
- }
-
- public Task Rendered => _rendered.Task;
-
- ///
- /// Gets a value identifying the scene's generation. This is incremented each time the scene is cloned.
- ///
- public int Generation { get; }
-
- ///
- /// Gets the layers for the scene.
- ///
- public SceneLayers Layers { get; }
-
- ///
- /// Gets the root node of the scene graph.
- ///
- public IVisualNode Root { get; }
-
- ///
- /// Gets or sets the size of the scene in device independent pixels.
- ///
- public Size Size { get; set; }
-
- ///
- /// Gets or sets the scene scaling.
- ///
- public double Scaling { get; set; } = 1;
-
- ///
- /// Adds a node to the scene index.
- ///
- /// The node.
- public void Add(IVisualNode node)
- {
- _ = node ?? throw new ArgumentNullException(nameof(node));
-
- _index.Add(node.Visual, node);
- }
-
- ///
- /// Clones the scene.
- ///
- /// The cloned scene.
- public Scene CloneScene()
- {
- var index = new Dictionary(_index.Count);
- var root = Clone((VisualNode)Root, null, index);
-
- var result = new Scene(root, index, Layers.Clone(), Generation + 1)
- {
- Size = Size,
- Scaling = Scaling,
- };
-
- return result;
- }
-
- public void Dispose()
- {
- _rendered.TrySetResult(false);
- foreach (var node in _index.Values)
- {
- node.Dispose();
- }
- }
-
- ///
- /// Tries to find a node in the scene graph representing the specified visual.
- ///
- /// The visual.
- ///
- /// The node representing the visual or null if it could not be found.
- ///
- public IVisualNode? FindNode(Visual visual)
- {
- _index.TryGetValue(visual, out var node);
- return node;
- }
-
- ///
- /// Gets the visuals at a point in the scene.
- ///
- /// The point.
- /// The root of the subtree to search.
- /// A filter. May be null.
- /// The visuals at the specified point.
- public IEnumerable HitTest(Point p, Visual root, Func? filter)
- {
- var node = FindNode(root);
- return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty();
- }
-
- ///
- /// Gets the visual at a point in the scene.
- ///
- /// The point.
- /// The root of the subtree to search.
- /// A filter. May be null.
- /// The visual at the specified point.
- public Visual? HitTestFirst(Point p, Visual root, Func? filter)
- {
- var node = FindNode(root);
- return (node != null) ? HitTestFirst(node, p, filter) : null;
- }
-
- ///
- /// Removes a node from the scene index.
- ///
- /// The node.
- public void Remove(IVisualNode node)
- {
- _ = node ?? throw new ArgumentNullException(nameof(node));
-
- _index.Remove(node.Visual);
-
- node.Dispose();
- }
-
- private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index)
- {
- var result = source.Clone(parent);
-
- index.Add(result.Visual, result);
-
- var children = source.Children;
- var childrenCount = children.Count;
-
- if (childrenCount > 0)
- {
- result.TryPreallocateChildren(childrenCount);
-
- for (var i = 0; i < childrenCount; i++)
- {
- var child = children[i];
-
- result.AddChild(Clone((VisualNode)child, result, index));
- }
- }
-
- return result;
- }
-
- private Visual HitTestFirst(IVisualNode root, Point p, Func? filter)
- {
- using var enumerator = new HitTestEnumerator(root, filter, p, Root);
-
- enumerator.MoveNext();
-
- return enumerator.Current;
- }
-
- private class HitTestEnumerable : IEnumerable
- {
- private readonly IVisualNode _root;
- private readonly Func? _filter;
- private readonly IVisualNode _sceneRoot;
- private readonly Point _point;
-
- public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot)
- {
- _root = root;
- _filter = filter;
- _point = point;
- _sceneRoot = sceneRoot;
- }
-
- public IEnumerator GetEnumerator()
- {
- return new HitTestEnumerator(_root, _filter, _point, _sceneRoot);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
-
- private struct HitTestEnumerator : IEnumerator
- {
- private readonly PooledStack _nodeStack;
- private readonly Func? _filter;
- private readonly IVisualNode _sceneRoot;
- private Visual? _current;
- private readonly Point _point;
-
- public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot)
- {
- _nodeStack = new PooledStack();
- _nodeStack.Push(new Entry(root, false, null, true));
-
- _filter = filter;
- _point = point;
- _sceneRoot = sceneRoot;
-
- _current = null;
- }
-
- public bool MoveNext()
- {
- while (_nodeStack.Count > 0)
- {
- (var wasVisited, var isRoot, IVisualNode node, Rect? clip) = _nodeStack.Pop();
-
- if (wasVisited && isRoot)
- {
- break;
- }
-
- var children = node.Children;
- int childCount = children.Count;
-
- if (childCount == 0 || wasVisited)
- {
- if ((wasVisited || FilterAndClip(node, ref clip)) &&
- (node.Visual is ICustomHitTest custom ? custom.HitTest(_point) : node.HitTest(_point)))
- {
- _current = node.Visual;
-
- return true;
- }
- }
- else if (FilterAndClip(node, ref clip))
- {
- _nodeStack.Push(new Entry(node, true, null));
-
- for (var i = 0; i < childCount; i++)
- {
- _nodeStack.Push(new Entry(children[i], false, clip));
- }
- }
- }
-
- return false;
- }
-
- public void Reset()
- {
- throw new NotSupportedException();
- }
-
- public Visual Current => _current!;
-
- object IEnumerator.Current => Current;
-
- public void Dispose()
- {
- _nodeStack.Dispose();
- }
-
- private bool FilterAndClip(IVisualNode node, ref Rect? clip)
- {
- if (_filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
- {
- var clipped = false;
-
- if (node.ClipToBounds)
- {
- clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
- clipped = !clip.Value.ContainsExclusive(_point);
- }
-
- if (node.GeometryClip != null)
- {
- var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual);
- clipped = !node.GeometryClip.FillContains(controlPoint!.Value);
- }
-
- if (!clipped && node.Visual is ICustomHitTest custom)
- {
- clipped = !custom.HitTest(_point);
- }
-
- return !clipped;
- }
-
- return false;
- }
-
- private readonly struct Entry
- {
- public readonly bool WasVisited;
- public readonly bool IsRoot;
- public readonly IVisualNode Node;
- public readonly Rect? Clip;
-
- public Entry(IVisualNode node, bool wasVisited, Rect? clip, bool isRoot = false)
- {
- Node = node;
- WasVisited = wasVisited;
- IsRoot = isRoot;
- Clip = clip;
- }
-
- public void Deconstruct(out bool wasVisited, out bool isRoot, out IVisualNode node, out Rect? clip)
- {
- wasVisited = WasVisited;
- isRoot = IsRoot;
- node = Node;
- clip = Clip;
- }
- }
- }
-
- public void MarkAsRendered() => _rendered.TrySetResult(true);
-
- public List? RenderThreadJobs { get; set; }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs
deleted file mode 100644
index 55ff772772..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs
+++ /dev/null
@@ -1,485 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Threading;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// Builds a scene graph from a visual tree.
- ///
- public class SceneBuilder : ISceneBuilder
- {
- ///
- public void UpdateAll(Scene scene)
- {
- _ = scene ?? throw new ArgumentNullException(nameof(scene));
- Dispatcher.UIThread.VerifyAccess();
-
- UpdateSize(scene);
- scene.Layers.GetOrAdd(scene.Root.Visual);
-
- using (var impl = new DeferredDrawingContextImpl(this, scene.Layers))
- using (var context = new DrawingContext(impl))
- {
- var clip = new Rect(scene.Root.Visual.Bounds.Size);
- Update(context, scene, (VisualNode)scene.Root, clip, true);
- }
- }
-
- ///
- public bool Update(Scene scene, Visual visual)
- {
- _ = scene ?? throw new ArgumentNullException(nameof(scene));
- _ = visual ?? throw new ArgumentNullException(nameof(visual));
-
- Dispatcher.UIThread.VerifyAccess();
-
- if (!scene.Root.Visual.IsVisible)
- {
- throw new AvaloniaInternalException("Cannot update the scene for an invisible root visual.");
- }
-
- var node = (VisualNode?)scene.FindNode(visual);
-
- if (visual == scene.Root.Visual)
- {
- UpdateSize(scene);
- }
-
- if (visual.VisualRoot == scene.Root.Visual)
- {
- if (node?.Parent != null &&
- visual.VisualParent != null &&
- node.Parent.Visual != visual.VisualParent)
- {
- // The control has changed parents. Remove the node and recurse into the new parent node.
- ((VisualNode)node.Parent).RemoveChild(node);
- Deindex(scene, node);
- node = (VisualNode?)scene.FindNode(visual.VisualParent);
- }
-
- if (visual.IsVisible)
- {
- // If the node isn't yet part of the scene, find the nearest ancestor that is.
- node = node ?? FindExistingAncestor(scene, visual);
-
- // We don't need to do anything if this part of the tree has already been fully
- // updated.
- if (node != null && !node.SubTreeUpdated)
- {
- // If the control we've been asked to update isn't part of the scene then
- // we're carrying out an add operation, so recurse and add all the
- // descendents too.
- var recurse = node.Visual != visual;
-
- using (var impl = new DeferredDrawingContextImpl(this, scene.Layers))
- using (var context = new DrawingContext(impl))
- {
- var clip = new Rect(scene.Root.Visual.Bounds.Size);
-
- if (node.Parent != null)
- {
- context.PushPostTransform(node.Parent.Transform);
- clip = node.Parent.ClipBounds;
- }
-
- using (context.PushTransformContainer())
- {
- Update(context, scene, node, clip, recurse);
- }
- }
-
- return true;
- }
- }
- else
- {
- if (node != null)
- {
- // The control has been hidden so remove it from its parent and deindex the
- // node and its descendents.
- ((VisualNode?)node.Parent)?.RemoveChild(node);
- Deindex(scene, node);
- return true;
- }
- }
- }
- else if (node != null)
- {
- // The control has been removed so remove it from its parent and deindex the
- // node and its descendents.
- var trim = FindFirstDeadAncestor(scene, node);
- ((VisualNode)trim.Parent!).RemoveChild(trim);
- Deindex(scene, trim);
- return true;
- }
-
- return false;
- }
-
- private static VisualNode? FindExistingAncestor(Scene scene, Visual visual)
- {
- var node = scene.FindNode(visual);
-
- while (node == null && visual.IsVisible)
- {
- var parent = visual.VisualParent;
-
- if (parent is null)
- return null;
-
- visual = parent;
- node = scene.FindNode(visual);
- }
-
- return visual.IsVisible ? (VisualNode?)node : null;
- }
-
- private static VisualNode FindFirstDeadAncestor(Scene scene, IVisualNode node)
- {
- var parent = node.Parent;
-
- while (parent!.Visual.VisualRoot == null)
- {
- node = parent;
- parent = node.Parent;
- }
-
- return (VisualNode)node;
- }
-
- private static object GetOrCreateChildNode(Scene scene, Visual child, VisualNode parent)
- {
- var result = (VisualNode?)scene.FindNode(child);
-
- if (result != null && result.Parent != parent)
- {
- Deindex(scene, result);
- ((VisualNode?)result.Parent)?.RemoveChild(result);
- result = null;
- }
-
- return result ?? CreateNode(scene, child, parent);
- }
-
- private static void Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, bool forceRecurse)
- {
- var visual = node.Visual;
- var opacity = visual.Opacity;
- var clipToBounds = visual.ClipToBounds;
-#pragma warning disable CS0618 // Type or member is obsolete
- var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ?
- roundRectClip.ClipToBoundsRadius :
- default;
-#pragma warning restore CS0618 // Type or member is obsolete
-
- var bounds = new Rect(visual.Bounds.Size);
- var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
-
- contextImpl.Layers.Find(node.LayerRoot!)?.Dirty.Add(node.Bounds);
-
- if (visual.IsVisible)
- {
- var m = node != scene.Root ?
- Matrix.CreateTranslation(visual.Bounds.Position) :
- Matrix.Identity;
-
- var renderTransform = Matrix.Identity;
-
- // this should be calculated BEFORE renderTransform
- if (visual.HasMirrorTransform)
- {
- var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
- renderTransform *= mirrorMatrix;
- }
-
- if (visual.RenderTransform != null)
- {
- var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
- var offset = Matrix.CreateTranslation(origin);
- var finalTransform = (-offset) * visual.RenderTransform.Value * (offset);
- renderTransform *= finalTransform;
- }
-
- m = renderTransform * m;
-
- using (contextImpl.BeginUpdate(node))
- using (context.PushPostTransform(m))
- using (context.PushTransformContainer())
- {
- var globalBounds = bounds.TransformToAABB(contextImpl.Transform);
- var clipBounds = clipToBounds ?
- globalBounds.Intersect(clip) :
- clip;
-
- forceRecurse = forceRecurse ||
- node.ClipBounds != clipBounds ||
- node.Opacity != opacity ||
- node.Transform != contextImpl.Transform;
-
- node.Transform = contextImpl.Transform;
- node.ClipBounds = clipBounds;
- node.ClipToBounds = clipToBounds;
- node.LayoutBounds = globalBounds;
- node.ClipToBoundsRadius = clipToBoundsRadius;
- node.GeometryClip = visual.Clip?.PlatformImpl;
- node.Opacity = opacity;
-
- // TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning.
- node.OpacityMask = visual.OpacityMask?.ToImmutable();
-
- if (ShouldStartLayer(visual))
- {
- if (node.LayerRoot != visual)
- {
- MakeLayer(scene, node);
- }
- else
- {
- UpdateLayer(node, scene.Layers[node.LayerRoot]);
- }
- }
- else if (node.LayerRoot == node.Visual && node.Parent != null)
- {
- ClearLayer(scene, node);
- }
-
- if (node.ClipToBounds)
- {
- clip = clip.Intersect(node.ClipBounds);
- }
-
- try
- {
- visual.Render(context);
- }
- catch { }
-
- var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform);
- visual.SetTransformedBounds(transformed);
-
- if (forceRecurse)
- {
- var visualChildren = (IList) visual.VisualChildren;
-
- node.TryPreallocateChildren(visualChildren.Count);
-
- if (visualChildren.Count == 1)
- {
- var childNode = GetOrCreateChildNode(scene, visualChildren[0], node);
- Update(context, scene, (VisualNode)childNode, clip, forceRecurse);
- }
- else if (visualChildren.Count > 1)
- {
- var count = visualChildren.Count;
-
- if (visual.HasNonUniformZIndexChildren)
- {
- var sortedChildren = new (Visual visual, int index)[count];
-
- for (var i = 0; i < count; i++)
- {
- sortedChildren[i] = (visualChildren[i], i);
- }
-
- // Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements.
- Array.Sort(sortedChildren, (lhs, rhs) =>
- {
- var result = ZIndexComparer.Instance.Compare(lhs.visual, rhs.visual);
-
- return result == 0 ? lhs.index.CompareTo(rhs.index) : result;
- });
-
- foreach (var child in sortedChildren)
- {
- var childNode = GetOrCreateChildNode(scene, child.Item1, node);
- Update(context, scene, (VisualNode)childNode, clip, forceRecurse);
- }
- }
- else
- foreach (var child in visualChildren)
- {
- var childNode = GetOrCreateChildNode(scene, child, node);
- Update(context, scene, (VisualNode)childNode, clip, forceRecurse);
- }
- }
-
- node.SubTreeUpdated = true;
- contextImpl.TrimChildren();
- }
- }
- }
- else
- {
- contextImpl.BeginUpdate(node).Dispose();
- }
- }
-
- private static void UpdateSize(Scene scene)
- {
- var renderRoot = scene.Root.Visual as IRenderRoot;
- var newSize = renderRoot?.ClientSize ?? scene.Root.Visual.Bounds.Size;
-
- scene.Scaling = renderRoot?.RenderScaling ?? 1;
-
- if (scene.Size != newSize)
- {
- var oldSize = scene.Size;
-
- scene.Size = newSize;
-
- Rect horizontalDirtyRect = default;
- Rect verticalDirtyRect = default;
-
- if (newSize.Width > oldSize.Width)
- {
- horizontalDirtyRect = new Rect(oldSize.Width, 0, newSize.Width - oldSize.Width, oldSize.Height);
- }
-
- if (newSize.Height > oldSize.Height)
- {
- verticalDirtyRect = new Rect(0, oldSize.Height, newSize.Width, newSize.Height - oldSize.Height);
- }
-
- foreach (var layer in scene.Layers)
- {
- layer.Dirty.Add(horizontalDirtyRect);
- layer.Dirty.Add(verticalDirtyRect);
- }
- }
- }
-
- private static VisualNode CreateNode(Scene scene, Visual visual, VisualNode parent)
- {
- var node = new VisualNode(visual, parent);
- node.LayerRoot = parent.LayerRoot;
- scene.Add(node);
- return node;
- }
-
- private static void Deindex(Scene scene, VisualNode node)
- {
- var nodeChildren = node.Children;
- var nodeChildrenCount = nodeChildren.Count;
-
- for (var i = 0; i < nodeChildrenCount; i++)
- {
- if (nodeChildren[i] is VisualNode visual)
- {
- Deindex(scene, visual);
- }
- }
-
- scene.Remove(node);
-
- node.SubTreeUpdated = true;
-
- scene.Layers[node.LayerRoot!].Dirty.Add(node.Bounds);
-
- node.Visual.SetTransformedBounds(null);
-
- if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual)
- {
- scene.Layers.Remove(node.LayerRoot);
- }
- }
-
- private static void ClearLayer(Scene scene, VisualNode node)
- {
- var parent = (VisualNode)node.Parent!;
- var oldLayerRoot = node.LayerRoot;
- var newLayerRoot = parent.LayerRoot!;
- var existingDirtyRects = scene.Layers[node.LayerRoot!].Dirty;
- var newDirtyRects = scene.Layers[newLayerRoot].Dirty;
-
- existingDirtyRects.Coalesce();
-
- foreach (var r in existingDirtyRects)
- {
- newDirtyRects.Add(r);
- }
-
- var oldLayer = scene.Layers[oldLayerRoot!];
- PropagateLayer(node, scene.Layers[newLayerRoot], oldLayer);
- scene.Layers.Remove(oldLayer);
- }
-
- private static void MakeLayer(Scene scene, VisualNode node)
- {
- var oldLayerRoot = node.LayerRoot!;
- var layer = scene.Layers.Add(node.Visual);
- var oldLayer = scene.Layers[oldLayerRoot!];
-
- UpdateLayer(node, layer);
- PropagateLayer(node, layer, scene.Layers[oldLayerRoot]);
- }
-
- private static void UpdateLayer(VisualNode node, SceneLayer layer)
- {
- layer.Opacity = node.Visual.Opacity;
-
- if (node.Visual.OpacityMask != null)
- {
- layer.OpacityMask = node.Visual.OpacityMask?.ToImmutable();
- layer.OpacityMaskRect = node.ClipBounds;
- }
- else
- {
- layer.OpacityMask = null;
- layer.OpacityMaskRect = default;
- }
-
- layer.GeometryClip = node.HasAncestorGeometryClip ?
- CreateLayerGeometryClip(node) :
- null;
- }
-
- private static void PropagateLayer(VisualNode node, SceneLayer layer, SceneLayer oldLayer)
- {
- node.LayerRoot = layer.LayerRoot;
-
- layer.Dirty.Add(node.Bounds);
- oldLayer.Dirty.Add(node.Bounds);
-
- foreach (VisualNode child in node.Children)
- {
- // If the child is not the start of a new layer, recurse.
- if (child.LayerRoot != child.Visual)
- {
- PropagateLayer(child, layer, oldLayer);
- }
- }
- }
-
- // HACK: Disabled layers because they're broken in current renderer. See #2244.
- private static bool ShouldStartLayer(Visual visual) => false;
-
- private static IGeometryImpl? CreateLayerGeometryClip(VisualNode node)
- {
- IGeometryImpl? result = null;
- VisualNode? n = node;
-
- for (;;)
- {
- n = (VisualNode?)n!.Parent;
-
- if (n == null || (n.GeometryClip == null && !n.HasAncestorGeometryClip))
- {
- break;
- }
-
- if (n?.GeometryClip != null)
- {
- var transformed = n.GeometryClip.WithTransform(n.Transform);
-
- result = result == null ? transformed : result.Intersect(transformed);
- }
- }
-
- return result;
- }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs
deleted file mode 100644
index a5e3b88188..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using Avalonia.Media;
-using Avalonia.Platform;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// Represents a layer in a .
- ///
- public class SceneLayer
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The visual at the root of the layer.
- /// The distance from the scene root.
- public SceneLayer(Visual layerRoot, int distanceFromRoot)
- {
- LayerRoot = layerRoot;
- Dirty = new DirtyRects();
- DistanceFromRoot = distanceFromRoot;
- }
-
- ///
- /// Clones the layer.
- ///
- /// The cloned layer.
- public SceneLayer Clone()
- {
- return new SceneLayer(LayerRoot, DistanceFromRoot)
- {
- Opacity = Opacity,
- OpacityMask = OpacityMask,
- OpacityMaskRect = OpacityMaskRect,
- GeometryClip = GeometryClip,
- };
- }
-
- ///
- /// Gets the visual at the root of the layer.
- ///
- public Visual LayerRoot { get; }
-
- ///
- /// Gets the distance of the layer root from the root of the scene.
- ///
- public int DistanceFromRoot { get; }
-
- ///
- /// Gets or sets the opacity of the layer.
- ///
- public double Opacity { get; set; } = 1;
-
- ///
- /// Gets or sets the opacity mask for the layer.
- ///
- public IBrush? OpacityMask { get; set; }
-
- ///
- /// Gets or sets the target rectangle for the layer opacity mask.
- ///
- public Rect OpacityMaskRect { get; set; }
-
- ///
- /// Gets the layer's geometry clip.
- ///
- public IGeometryImpl? GeometryClip { get; set; }
-
- ///
- /// Gets the dirty rectangles for the layer.
- ///
- internal DirtyRects Dirty { get; }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs
deleted file mode 100644
index 8a997c5ace..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs
+++ /dev/null
@@ -1,206 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// Holds a collection of layers for a .
- ///
- public class SceneLayers : IEnumerable
- {
- private readonly Visual _root;
- private readonly List _inner;
- private readonly Dictionary _index;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The scene's root visual.
- public SceneLayers(Visual root) : this(root, 0)
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The scene's root visual.
- /// Initial layer capacity.
- public SceneLayers(Visual root, int capacity)
- {
- _root = root;
-
- _inner = new List(capacity);
- _index = new Dictionary(capacity);
- }
-
- ///
- /// Gets the number of layers in the scene.
- ///
- public int Count => _inner.Count;
-
- ///
- /// Gets a value indicating whether any of the layers have a dirty region.
- ///
- public bool HasDirty
- {
- get
- {
- foreach (var layer in _inner)
- {
- if (!layer.Dirty.IsEmpty)
- {
- return true;
- }
- }
-
- return false;
- }
- }
-
- ///
- /// Gets a layer by index.
- ///
- /// The index of the layer.
- /// The layer.
- public SceneLayer this[int index] => _inner[index];
-
- ///
- /// Gets a layer by its root visual.
- ///
- /// The layer's root visual.
- /// The layer.
- public SceneLayer this[Visual visual] => _index[visual];
-
- ///
- /// Adds a layer to the scene.
- ///
- /// The root visual of the layer.
- /// The created layer.
- public SceneLayer Add(Visual layerRoot)
- {
- _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot));
-
- var distance = layerRoot.CalculateDistanceFromAncestor(_root);
- var layer = new SceneLayer(layerRoot, distance);
- var insert = FindInsertIndex(layer);
- _index.Add(layerRoot, layer);
- _inner.Insert(insert, layer);
- return layer;
- }
-
- ///
- /// Makes a deep clone of the layers.
- ///
- /// The cloned layers.
- public SceneLayers Clone()
- {
- var result = new SceneLayers(_root, Count);
-
- foreach (var src in _inner)
- {
- var dest = src.Clone();
- result._index.Add(dest.LayerRoot, dest);
- result._inner.Add(dest);
- }
-
- return result;
- }
-
- ///
- /// Tests whether a layer exists with the specified root visual.
- ///
- /// The root visual.
- ///
- /// True if a layer exists with the specified root visual, otherwise false.
- ///
- public bool Exists(Visual layerRoot)
- {
- _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot));
-
- return _index.ContainsKey(layerRoot);
- }
-
- ///
- /// Tries to find a layer with the specified root visual.
- ///
- /// The root visual.
- /// The layer if found, otherwise null.
- public SceneLayer? Find(Visual layerRoot)
- {
- _index.TryGetValue(layerRoot, out var result);
- return result;
- }
-
- ///
- /// Gets an existing layer or creates a new one if no existing layer is found.
- ///
- /// The root visual.
- /// The layer.
- public SceneLayer GetOrAdd(Visual layerRoot)
- {
- _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot));
-
- if (!_index.TryGetValue(layerRoot, out var result))
- {
- result = Add(layerRoot);
- }
-
- return result;
- }
-
- ///
- /// Removes a layer from the scene.
- ///
- /// The root visual.
- /// True if a matching layer was removed, otherwise false.
- public bool Remove(Visual layerRoot)
- {
- _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot));
-
- if (_index.TryGetValue(layerRoot, out var layer))
- {
- Remove(layer);
- }
-
- return layer != null;
- }
-
- ///
- /// Removes a layer from the scene.
- ///
- /// The layer.
- /// True if the layer was part of the scene, otherwise false.
- public bool Remove(SceneLayer layer)
- {
- _ = layer ?? throw new ArgumentNullException(nameof(layer));
-
- _index.Remove(layer.LayerRoot);
- return _inner.Remove(layer);
- }
-
- ///
- public IEnumerator GetEnumerator() => _inner.GetEnumerator();
-
- ///
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- private int FindInsertIndex(SceneLayer insert)
- {
- var index = 0;
-
- foreach (var layer in _inner)
- {
- if (layer.DistanceFromRoot > insert.DistanceFromRoot)
- {
- break;
- }
-
- ++index;
- }
-
- return index;
- }
- }
-}
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs
deleted file mode 100644
index b9491e6cbd..0000000000
--- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs
+++ /dev/null
@@ -1,448 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using Avalonia.Reactive;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Utilities;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
- ///
- /// A node in the low-level scene graph representing an .
- ///
- internal class VisualNode : IVisualNode
- {
- private static readonly IReadOnlyList EmptyChildren = Array.Empty();
- private static readonly IReadOnlyList> EmptyDrawOperations = Array.Empty>();
-
- private Rect? _bounds;
- private double _opacity;
- private List? _children;
- private List>? _drawOperations;
- private IRef? _drawOperationsRefCounter;
- private bool _drawOperationsCloned;
- private Matrix transformRestore;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The visual that this node represents.
- /// The parent scene graph node, if any.
- public VisualNode(Visual visual, IVisualNode? parent)
- {
- Visual = visual ?? throw new ArgumentNullException(nameof(visual));
- Parent = parent;
- HasAncestorGeometryClip = parent != null &&
- (parent.HasAncestorGeometryClip || parent.GeometryClip != null);
- }
-
- ///
- public Visual Visual { get; }
-
- ///
- public IVisualNode? Parent { get; }
-
- ///
- public CornerRadius ClipToBoundsRadius { get; set; }
-
- ///
- public Matrix Transform { get; set; }
-
- ///
- public Rect Bounds => _bounds ?? CalculateBounds();
-
- ///
- public Rect ClipBounds { get; set; }
-
- ///
- public Rect LayoutBounds { get; set; }
-
- ///
- public bool ClipToBounds { get; set; }
-
- ///
- public IGeometryImpl? GeometryClip { get; set; }
-
- ///
- public bool HasAncestorGeometryClip { get; }
-
- ///
- public double Opacity
- {
- get { return _opacity; }
- set
- {
- if (_opacity != value)
- {
- _opacity = value;
- OpacityChanged = true;
- }
- }
- }
-
- ///
- /// Gets or sets the opacity mask for the scene graph node.
- ///
- public IBrush? OpacityMask { get; set; }
-
- ///
- /// Gets a value indicating whether this node in the scene graph has already
- /// been updated in the current update pass.
- ///
- public bool SubTreeUpdated { get; set; }
-
- ///
- /// Gets a value indicating whether the property has changed.
- ///
- public bool OpacityChanged { get; private set; }
-
- public Visual? LayerRoot { get; set; }
-
- ///
- public IReadOnlyList Children => _children ?? EmptyChildren;
-
- ///
- public IReadOnlyList> DrawOperations => _drawOperations ?? EmptyDrawOperations;
-
- ///
- /// Adds a child to the collection.
- ///
- /// The child to add.
- public void AddChild(IVisualNode child)
- {
- if (child.Disposed)
- {
- throw new ObjectDisposedException("Visual node for {node.Visual}");
- }
-
- if (child.Parent != this)
- {
- throw new AvaloniaInternalException("VisualNode added to wrong parent.");
- }
-
- EnsureChildrenCreated();
- _children.Add(child);
- }
-
- ///
- /// Adds an operation to the collection.
- ///
- /// The operation to add.
- public void AddDrawOperation(IRef operation)
- {
- EnsureDrawOperationsCreated();
- _drawOperations.Add(operation.Clone());
- }
-
- ///
- /// Removes a child from the collection.
- ///
- /// The child to remove.
- public void RemoveChild(IVisualNode child)
- {
- EnsureChildrenCreated();
- _children.Remove(child);
- }
-
- ///
- /// Replaces a child in the collection.
- ///
- /// The child to be replaced.
- /// The child to add.
- public void ReplaceChild(int index, IVisualNode node)
- {
- if (node.Disposed)
- {
- throw new ObjectDisposedException("Visual node for {node.Visual}");
- }
-
- if (node.Parent != this)
- {
- throw new AvaloniaInternalException("VisualNode added to wrong parent.");
- }
-
- EnsureChildrenCreated();
- _children[index] = node;
- }
-
- ///
- /// Replaces an item in the collection.
- ///
- /// The operation to be replaced.
- /// The operation to add.
- public void ReplaceDrawOperation(int index, IRef operation)
- {
- EnsureDrawOperationsCreated();
- var old = _drawOperations[index];
- _drawOperations[index] = operation.Clone();
- old.Dispose();
- }
-
- ///
- /// Sorts the collection according to the order of the visual's
- /// children and their z-index.
- ///
- /// The scene that the node is a part of.
- public void SortChildren(Scene scene)
- {
- if (_children == null || _children.Count <= 1)
- {
- return;
- }
-
- var keys = new List(Visual.VisualChildren.Count);
-
- for (var i = 0; i < Visual.VisualChildren.Count; ++i)
- {
- var child = Visual.VisualChildren[i];
- var zIndex = child.ZIndex;
- keys.Add(((long)zIndex << 32) + i);
- }
-
- keys.Sort();
- _children.Clear();
-
- foreach (var i in keys)
- {
- var child = Visual.VisualChildren[(int)(i & 0xffffffff)];
- var node = scene.FindNode(child);
-
- if (node != null)
- {
- _children.Add(node);
- }
- }
- }
-
- ///
- /// Removes items in the collection from the specified index
- /// to the end.
- ///
- /// The index of the first child to be removed.
- public void TrimChildren(int first)
- {
- if (first < _children?.Count)
- {
- EnsureChildrenCreated();
- for (int i = first; i < _children.Count; i++)
- {
- _children[i].Dispose();
- }
- _children.RemoveRange(first, _children.Count - first);
- }
- }
-
- ///
- /// Removes items in the collection from the specified index
- /// to the end.
- ///
- /// The index of the first operation to be removed.
- public void TrimDrawOperations(int first)
- {
- if (first < _drawOperations?.Count)
- {
- EnsureDrawOperationsCreated();
- for (int i = first; i < _drawOperations.Count; i++)
- {
- _drawOperations[i].Dispose();
- }
- _drawOperations.RemoveRange(first, _drawOperations.Count - first);
- }
- }
-
- ///
- /// Makes a copy of the node
- ///
- /// The new parent node.
- /// A cloned node.
- public VisualNode Clone(IVisualNode? parent)
- {
- return new VisualNode(Visual, parent)
- {
- Transform = Transform,
- ClipBounds = ClipBounds,
- ClipToBoundsRadius = ClipToBoundsRadius,
- ClipToBounds = ClipToBounds,
- LayoutBounds = LayoutBounds,
- GeometryClip = GeometryClip,
- _opacity = Opacity,
- OpacityMask = OpacityMask,
- _drawOperations = _drawOperations,
- _drawOperationsRefCounter = _drawOperationsRefCounter?.Clone(),
- _drawOperationsCloned = true,
- LayerRoot= LayerRoot,
- };
- }
-
- ///
- public bool HitTest(Point p)
- {
- var drawOperations = DrawOperations;
- var drawOperationsCount = drawOperations.Count;
-
- for (var i = 0; i < drawOperationsCount; i++)
- {
- var operation = drawOperations[i];
-
- if (operation?.Item?.HitTest(p) == true)
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
- {
- transformRestore = context.Transform;
-
- if (ClipToBounds)
- {
- context.Transform = Matrix.Identity;
- if (ClipToBoundsRadius.IsDefault)
- context.PushClip(ClipBounds);
- else
- context.PushClip(new RoundedRect(ClipBounds, ClipToBoundsRadius));
- }
-
- context.Transform = Transform;
-
- if (Opacity != 1 && !skipOpacity)
- {
- context.PushOpacity(Opacity);
- }
-
- if (GeometryClip != null)
- {
- context.PushGeometryClip(GeometryClip);
- }
-
- if (OpacityMask != null)
- {
- context.PushOpacityMask(OpacityMask, LayoutBounds);
- }
- }
-
- ///
- public void EndRender(IDrawingContextImpl context, bool skipOpacity)
- {
- if (OpacityMask != null)
- {
- context.PopOpacityMask();
- }
-
- if (GeometryClip != null)
- {
- context.PopGeometryClip();
- }
-
- if (Opacity != 1 && !skipOpacity)
- {
- context.PopOpacity();
- }
-
- if (ClipToBounds)
- {
- context.Transform = Matrix.Identity;
- context.PopClip();
- }
-
- context.Transform = transformRestore;
- }
-
- internal void TryPreallocateChildren(int count)
- {
- if (count == 0)
- {
- return;
- }
-
- EnsureChildrenCreated(count);
- }
-
- private Rect CalculateBounds()
- {
- var result = new Rect();
-
- if (_drawOperations != null)
- {
- foreach (var operation in _drawOperations)
- {
- result = result.Union(operation.Item.Bounds);
- }
- }
-
- _bounds = result;
- return result;
- }
-
- [MemberNotNull(nameof(_children))]
- private void EnsureChildrenCreated(int capacity = 0)
- {
- if (_children == null)
- {
- _children = new List(capacity);
- }
- }
-
- ///
- /// Ensures that this node draw operations have been created and are mutable (in case we are using cloned operations).
- ///
- [MemberNotNull(nameof(_drawOperations))]
- private void EnsureDrawOperationsCreated()
- {
- if (_drawOperations == null)
- {
- _drawOperations = new List>();
- _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
- _drawOperationsCloned = false;
- }
- else if (_drawOperationsCloned)
- {
- var oldDrawOperations = _drawOperations;
-
- _drawOperations = new List>(oldDrawOperations.Count);
-
- foreach (var drawOperation in oldDrawOperations)
- {
- _drawOperations.Add(drawOperation.Clone());
- }
-
- _drawOperationsRefCounter?.Dispose();
- _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
- _drawOperationsCloned = false;
- }
- }
-
- ///
- /// Creates disposable that will dispose all items in passed draw operations after being disposed.
- /// It is crucial that we don't capture current instance
- /// as draw operations can be cloned and may persist across subsequent scenes.
- ///
- /// Draw operations that need to be disposed.
- /// Disposable for given draw operations.
- private static IDisposable CreateDisposeDrawOperations(List> drawOperations)
- {
- return Disposable.Create(drawOperations, operations =>
- {
- foreach (var operation in operations)
- {
- operation.Dispose();
- }
- });
- }
-
- public bool Disposed { get; private set; }
-
- public void Dispose()
- {
- _drawOperationsRefCounter?.Dispose();
-
- Disposed = true;
- }
- }
-}
diff --git a/src/Avalonia.Base/Utilities/StopwatchHelper.cs b/src/Avalonia.Base/Utilities/StopwatchHelper.cs
new file mode 100644
index 0000000000..4719226ea4
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/StopwatchHelper.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Diagnostics;
+
+namespace Avalonia.Utilities;
+
+///
+/// Allows using as timestamps without allocating.
+///
+/// Equivalent to Stopwatch.GetElapsedTime in .NET 7.
+internal static class StopwatchHelper
+{
+ private static readonly double s_timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
+
+ public static TimeSpan GetElapsedTime(long startingTimestamp)
+ => GetElapsedTime(startingTimestamp, Stopwatch.GetTimestamp());
+
+ public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
+ => new((long)((endingTimestamp - startingTimestamp) * s_timestampToTicks));
+}
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 7fcb53bcea..e6d7492c51 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -36,12 +36,7 @@ namespace Avalonia
///
public static readonly DirectProperty BoundsProperty =
AvaloniaProperty.RegisterDirect(nameof(Bounds), o => o.Bounds);
-
- public static readonly DirectProperty TransformedBoundsProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(TransformedBounds),
- o => o.TransformedBounds);
-
+
///
/// Defines the property.
///
@@ -116,7 +111,6 @@ namespace Avalonia
(s, h) => s.Invalidated -= h);
private Rect _bounds;
- private TransformedBounds? _transformedBounds;
private IRenderRoot? _visualRoot;
private Visual? _visualParent;
private bool _hasMirrorTransform;
@@ -172,11 +166,6 @@ namespace Avalonia
protected set { SetAndRaise(BoundsProperty, ref _bounds, value); }
}
- ///
- /// Gets the bounds of the control relative to the window, accounting for rendering transforms.
- ///
- public TransformedBounds? TransformedBounds => _transformedBounds;
-
///
/// Gets or sets a value indicating whether the control should be clipped to its bounds.
///
@@ -523,11 +512,6 @@ namespace Avalonia
return CompositionVisual;
}
- internal void SetTransformedBounds(TransformedBounds? value)
- {
- SetAndRaise(TransformedBoundsProperty, ref _transformedBounds, value);
- }
-
///
/// Calls the method
/// for this control and all of its visual descendants.
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index a0dbf238dc..36fd9fe709 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -39,8 +39,8 @@
-
-
+
+
diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
index 8f532b9803..110590fef2 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
@@ -7,6 +7,7 @@ using Avalonia.Data;
using System;
using Avalonia.Controls.Utils;
using Avalonia.Markup.Xaml.MarkupExtensions;
+using Avalonia.Metadata;
using Avalonia.Reactive;
namespace Avalonia.Controls
@@ -24,6 +25,7 @@ namespace Avalonia.Controls
///
//TODO Binding
[AssignBinding]
+ [InheritDataTypeFromItems(nameof(DataGrid.Items), AncestorType = typeof(DataGrid))]
public virtual IBinding Binding
{
get
diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs
index 516e9cf6c2..00318e2dd8 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs
@@ -24,6 +24,7 @@ namespace Avalonia.Controls
(o, v) => o.CellTemplate = v);
[Content]
+ [InheritDataTypeFromItems(nameof(DataGrid.Items), AncestorType = typeof(DataGrid))]
public IDataTemplate CellTemplate
{
get { return _cellTemplate; }
@@ -50,6 +51,7 @@ namespace Avalonia.Controls
///
/// If this property is the column is read-only.
///
+ [InheritDataTypeFromItems(nameof(DataGrid.Items), AncestorType = typeof(DataGrid))]
public IDataTemplate CellEditingTemplate
{
get => _cellEditingCellTemplate;
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 1ec6f8dabc..9627f200df 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -394,10 +394,10 @@ namespace Avalonia.Controls
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
IsPressed = true;
- e.Handled = true;
if (ClickMode == ClickMode.Press)
{
+ e.Handled = true;
OnClick();
}
}
@@ -411,11 +411,11 @@ namespace Avalonia.Controls
if (IsPressed && e.InitialPressMouseButton == MouseButton.Left)
{
IsPressed = false;
- e.Handled = true;
if (ClickMode == ClickMode.Release &&
this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c)))
{
+ e.Handled = true;
OnClick();
}
}
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index 64f96b6987..3a4ae80cf4 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Threading;
namespace Avalonia.Controls.Embedding.Offscreen
{
@@ -13,7 +16,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
private double _scaling = 1;
private Size _clientSize;
- private PlatformRenderInterfaceContextManager _renderInterface = new(null);
+ private ManualRenderTimer _manualRenderTimer = new();
public IInputRoot? InputRoot { get; private set; }
public bool IsDisposed { get; private set; }
@@ -23,10 +26,19 @@ namespace Avalonia.Controls.Embedding.Offscreen
IsDisposed = true;
}
+ class ManualRenderTimer : IRenderTimer
+ {
+ static Stopwatch St = Stopwatch.StartNew();
+ public event Action? Tick;
+ public bool RunsInBackground => false;
+ public void TriggerTick() => Tick?.Invoke(St.Elapsed);
+ }
+
+
public IRenderer CreateRenderer(IRenderRoot root) =>
- new ImmediateRenderer((Visual)root, () => _renderInterface.CreateRenderTarget(Surfaces), _renderInterface);
+ new CompositingRenderer(root, new Compositor(new RenderLoop(_manualRenderTimer, Dispatcher.UIThread), null),
+ () => Surfaces);
- public abstract void Invalidate(Rect rect);
public abstract IEnumerable Surfaces { get; }
public Size ClientSize
@@ -82,5 +94,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public IPopupImpl? CreatePopup() => null;
+
+ public virtual object? TryGetFeature(Type featureType) => null;
}
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index db49da85e8..59b5bf48a5 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -168,6 +168,7 @@ namespace Avalonia.Controls
///
/// Gets or sets the data template used to display the items in the control.
///
+ [InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
get { return GetValue(ItemTemplateProperty); }
diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs
index 18dc1b1264..6b9e378d3d 100644
--- a/src/Avalonia.Controls/NativeControlHost.cs
+++ b/src/Avalonia.Controls/NativeControlHost.cs
@@ -58,7 +58,7 @@ namespace Avalonia.Controls
private void UpdateHost()
{
_queuedForMoveResize = false;
- _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
+ _currentHost = _currentRoot?.PlatformImpl?.TryGetFeature();
if (_currentHost != null)
{
diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs
index c556ce7b02..9c1fb93a48 100644
--- a/src/Avalonia.Controls/NativeMenu.Export.cs
+++ b/src/Avalonia.Controls/NativeMenu.Export.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Reactive;
+using Avalonia.Platform;
namespace Avalonia.Controls
{
@@ -21,7 +22,7 @@ namespace Avalonia.Controls
public NativeMenuInfo(TopLevel target)
{
- Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter;
+ Exporter = target.PlatformImpl?.TryGetFeature();
if (Exporter != null)
{
Exporter.OnIsNativeMenuExportedChanged += delegate
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index 3ccddf4155..b03099f750 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Controls.Notifications
///
[TemplatePart("PART_Items", typeof(Panel))]
[PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")]
- public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
+ public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
{
private IList? _items;
@@ -160,7 +160,5 @@ namespace Avalonia.Controls.Notifications
PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
}
-
- public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
}
}
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index e09da02f17..de3aca76d9 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -350,13 +350,19 @@ namespace Avalonia.Controls.Platform
{
// HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method.
var item = GetMenuItem(e.Source as Control) as MenuItem;
- if (item?.TransformedBounds == null)
- {
+
+ if (item == null)
return;
- }
+
+ var serverTransform = item?.CompositionVisual?.TryGetServerGlobalTransform();
+ if (serverTransform == null)
+ return;
+
var point = e.GetCurrentPoint(null);
+ var transformedPoint = point.Position.Transform(serverTransform.Value);
- if (point.Properties.IsLeftButtonPressed && item.TransformedBounds.Value.Contains(point.Position) == false)
+ if (point.Properties.IsLeftButtonPressed &&
+ new Rect(item!.Bounds.Size).Contains(transformedPoint) == false)
{
e.Pointer.Capture(null);
}
diff --git a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
index ffa79aa8d6..a2805e69e9 100644
--- a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
+++ b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
@@ -29,10 +29,4 @@ namespace Avalonia.Controls.Platform
void HideWithSize(Size size);
void ShowInBounds(Rect rect);
}
-
- [Unstable]
- public interface ITopLevelImplWithNativeControlHost
- {
- INativeControlHostImpl? NativeControlHost { get; }
- }
}
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
index bf74e0f8f4..29156f4030 100644
--- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
+++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
@@ -51,7 +51,7 @@ namespace Avalonia.Platform
/// .
///
[Unstable]
- public interface ITopLevelImpl : IDisposable
+ public interface ITopLevelImpl : IOptionalFeatureProvider, IDisposable
{
///
/// Gets the client size of the toplevel.
@@ -111,11 +111,6 @@ namespace Avalonia.Platform
/// The toplevel.
IRenderer CreateRenderer(IRenderRoot root);
- ///
- /// Invalidates a rect on the toplevel.
- ///
- void Invalidate(Rect rect);
-
///
/// Sets the for the toplevel.
///
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs b/src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs
deleted file mode 100644
index b42040f3c3..0000000000
--- a/src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Avalonia.Metadata;
-using Avalonia.Platform;
-using Avalonia.Platform.Storage;
-
-namespace Avalonia.Controls.Platform;
-
-[Unstable]
-public interface ITopLevelImplWithStorageProvider : ITopLevelImpl
-{
- public IStorageProvider StorageProvider { get; }
-}
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs b/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs
deleted file mode 100644
index a2e426ca08..0000000000
--- a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Avalonia.Input;
-using Avalonia.Input.TextInput;
-using Avalonia.Metadata;
-using Avalonia.Platform;
-
-namespace Avalonia.Controls.Platform
-{
- [Unstable]
- public interface ITopLevelImplWithTextInputMethod : ITopLevelImpl
- {
- public ITextInputMethodImpl? TextInputMethod { get; }
- }
-}
diff --git a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
index 149a978c54..3093169f04 100644
--- a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
+++ b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
@@ -23,10 +23,4 @@ namespace Avalonia.Controls.Platform
{
INativeMenuExporter? NativeMenuExporter { get; }
}
-
- [Unstable]
- public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl
- {
- ITopLevelNativeMenuExporter? NativeMenuExporter { get; }
- }
}
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 4c579cbd6e..762702efcc 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -607,30 +607,6 @@ namespace Avalonia.Controls.Presenters
if (Extent.Height > Viewport.Height)
{
double height = isLogical ? scrollable!.ScrollSize.Height : 50;
- if(VerticalSnapPointsType == SnapPointsType.MandatorySingle && Content is IScrollSnapPointsInfo)
- {
- if(_areVerticalSnapPointsRegular)
- {
- height = _verticalSnapPoint;
- }
- else if(_verticalSnapPoints != null && _verticalSnapPoints.Count > 0)
- {
- double yOffset = Offset.Y;
- switch (VerticalSnapPointsAlignment)
- {
- case SnapPointsAlignment.Center:
- yOffset += Viewport.Height / 2;
- break;
- case SnapPointsAlignment.Far:
- yOffset += Viewport.Height;
- break;
- }
-
- var snapPoint = FindNearestSnapPoint(_verticalSnapPoints, yOffset, out var lowerSnapPoint);
-
- height = snapPoint - lowerSnapPoint;
- }
- }
y += -delta.Y * height;
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
@@ -639,30 +615,6 @@ namespace Avalonia.Controls.Presenters
if (Extent.Width > Viewport.Width)
{
double width = isLogical ? scrollable!.ScrollSize.Width : 50;
- if (HorizontalSnapPointsType == SnapPointsType.MandatorySingle && Content is IScrollSnapPointsInfo)
- {
- if (_areHorizontalSnapPointsRegular)
- {
- width = _horizontalSnapPoint;
- }
- else if(_horizontalSnapPoints != null && _horizontalSnapPoints.Count > 0)
- {
- double xOffset = Offset.X;
- switch (VerticalSnapPointsAlignment)
- {
- case SnapPointsAlignment.Center:
- xOffset += Viewport.Width / 2;
- break;
- case SnapPointsAlignment.Far:
- xOffset += Viewport.Width;
- break;
- }
-
- var snapPoint = FindNearestSnapPoint(_horizontalSnapPoints, xOffset, out var lowerSnapPoint);
-
- width = snapPoint - lowerSnapPoint;
- }
- }
x += -delta.X * width;
x = Math.Max(x, 0);
x = Math.Min(x, Extent.Width - Viewport.Width);
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index c9585d50ae..3464857131 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives
///
/// TODO: Need to track position of adorned elements and move the adorner if they move.
///
- public class AdornerLayer : Canvas, ICustomSimpleHitTest
+ public class AdornerLayer : Canvas
{
///
/// Allows for getting and setting of the adorned element.
@@ -305,17 +305,9 @@ namespace Avalonia.Controls.Primitives
info.Bounds = new TransformedBounds(new Rect(adorned.Bounds.Size), new Rect(adorned.Bounds.Size), Matrix.Identity);
InvalidateMeasure();
});
- else
- info.Subscription = adorned.GetObservable(TransformedBoundsProperty).Subscribe(x =>
- {
- info.Bounds = x;
- InvalidateMeasure();
- });
}
}
- public bool HitTest(Point point) => Children.HitTestCustom(point);
-
private class AdornedElementInfo
{
public IDisposable? Subscription { get; set; }
diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
index 20b3b849a6..74b5beecad 100644
--- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
@@ -4,7 +4,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
- public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest
+ public class ChromeOverlayLayer : Panel
{
public static Panel? GetOverlayLayer(Visual visual)
{
@@ -26,7 +26,5 @@ namespace Avalonia.Controls.Primitives
{
base.Children.Add(c);
}
-
- public bool HitTest(Point point) => Children.HitTestCustom(point);
}
}
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 91136cb295..76b56f3a11 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -4,7 +4,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
- public class OverlayLayer : Canvas, ICustomSimpleHitTest
+ public class OverlayLayer : Canvas
{
public Size AvailableSize { get; private set; }
public static OverlayLayer? GetOverlayLayer(Visual visual)
@@ -22,8 +22,6 @@ namespace Avalonia.Controls.Primitives
return null;
}
- public bool HitTest(Point point) => Children.HitTestCustom(point);
-
protected override Size MeasureOverride(Size availableSize)
{
foreach (Control child in Children)
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 7cac12eabe..c85199a665 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -8,6 +8,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@@ -378,11 +379,8 @@ namespace Avalonia.Controls.Primitives
if (InheritsTransform && placementTarget is Control c)
{
- SubscribeToEventHandler>(
- c,
- PlacementTargetPropertyChanged,
- (x, handler) => x.PropertyChanged += handler,
- (x, handler) => x.PropertyChanged -= handler).DisposeWith(handlerCleanup);
+ TransformTrackingHelper.Track(c, PlacementTargetTransformChanged)
+ .DisposeWith(handlerCleanup);
}
else
{
@@ -872,13 +870,11 @@ namespace Avalonia.Controls.Primitives
Close();
}
}
-
- private void PlacementTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+
+ private void PlacementTargetTransformChanged(Visual v, Matrix? matrix)
{
- if (_openState is not null && e.Property == Visual.TransformedBoundsProperty)
- {
+ if (_openState is not null)
UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);
- }
}
private void WindowLostFocus()
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index 27578ddc78..5cefb0d89f 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -2,6 +2,7 @@
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Media.Imaging;
+using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
@@ -72,7 +73,7 @@ namespace Avalonia.Controls.Remote
{
if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{
- var fmt = (PixelFormat) _lastFrame.Format;
+ var fmt = new PixelFormat((PixelFormatEnum) _lastFrame.Format);
if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||
_bitmap.PixelSize.Height != _lastFrame.Height)
{
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index bc11c35fde..5519323033 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
@@ -11,6 +11,7 @@ using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
+using Avalonia.Rendering;
using Avalonia.Threading;
using Key = Avalonia.Input.Key;
using PixelFormat = Avalonia.Platform.PixelFormat;
@@ -19,7 +20,7 @@ using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
namespace Avalonia.Controls.Remote.Server
{
[Unstable]
- public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface
+ internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl
{
private readonly IAvaloniaRemoteTransportConnection _transport;
private LockedFramebuffer? _framebuffer;
@@ -28,7 +29,8 @@ namespace Avalonia.Controls.Remote.Server
private long _lastReceivedFrame = -1;
private long _nextFrameNumber = 1;
private ClientViewportAllocatedMessage? _pendingAllocation;
- private bool _invalidated;
+ private bool _queuedNextRender;
+ private bool _inRender;
private Vector _dpi = new Vector(96, 96);
private ProtocolPixelFormat[]? _supportedFormats;
@@ -38,6 +40,14 @@ namespace Avalonia.Controls.Remote.Server
_transport.OnMessage += OnMessage;
KeyboardDevice = AvaloniaLocator.Current.GetRequiredService();
+ QueueNextRender();
+ }
+
+ IRenderer ITopLevelImpl.CreateRenderer(IRenderRoot root)
+ {
+ var r = (IRendererWithCompositor)base.CreateRenderer(root);
+ r.Compositor.AfterCommit += QueueNextRender;
+ return r;
}
private static RawPointerEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
@@ -125,7 +135,7 @@ namespace Avalonia.Controls.Remote.Server
lock(_lock)
{
_dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY);
- _invalidated = true;
+ _queuedNextRender = true;
}
Dispatcher.UIThread.Post(RenderIfNeeded);
@@ -282,7 +292,7 @@ namespace Avalonia.Controls.Remote.Server
{
if (width > 0 && height > 0)
{
- _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt,
+ _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, new((PixelFormatEnum)fmt),
null);
Paint?.Invoke(new Rect(0, 0, width, height));
}
@@ -315,7 +325,7 @@ namespace Avalonia.Controls.Remote.Server
{
lock (_lock)
{
- if (_lastReceivedFrame != _lastSentFrame || !_invalidated || _supportedFormats == null)
+ if (_lastReceivedFrame != _lastSentFrame || !_queuedNextRender || _supportedFormats == null)
return;
}
@@ -327,23 +337,25 @@ namespace Avalonia.Controls.Remote.Server
format = fmt;
break;
}
-
+
+ _inRender = true;
var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format);
lock (_lock)
{
_lastSentFrame = _nextFrameNumber++;
frame.SequenceId = _lastSentFrame;
- _invalidated = false;
+ _queuedNextRender = false;
}
+ _inRender = false;
_transport.Send(frame);
}
- public override void Invalidate(Rect rect)
+ private void QueueNextRender()
{
- if (!IsDisposed)
+ if (!_inRender && !IsDisposed)
{
- _invalidated = true;
- Dispatcher.UIThread.Post(RenderIfNeeded);
+ _queuedNextRender = true;
+ DispatcherTimer.RunOnce(RenderIfNeeded, TimeSpan.FromMilliseconds(2), DispatcherPriority.Background);
}
}
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index 4de6a5188d..6c761ab4cf 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -11,6 +11,7 @@ using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
+using Avalonia.Metadata;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@@ -121,6 +122,7 @@ namespace Avalonia.Controls
///
/// Gets or sets the template used to display each item.
///
+ [InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index e6873693c4..7fe82a452e 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -1,7 +1,7 @@
using System;
+using System.ComponentModel;
using Avalonia.Reactive;
using Avalonia.Controls.Metadata;
-using Avalonia.Controls.Notifications;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
@@ -18,7 +18,6 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
-using Avalonia.VisualTree;
using Avalonia.Input.Platform;
using System.Linq;
@@ -108,6 +107,7 @@ namespace Avalonia.Controls
private Border? _transparencyFallbackBorder;
private TargetWeakEventSubscriber? _resourcesChangesSubscriber;
private IStorageProvider? _storageProvider;
+ private LayoutDiagnosticBridge? _layoutDiagnosticBridge;
///
/// Initializes static members of the class.
@@ -192,7 +192,7 @@ namespace Avalonia.Controls
ClientSize = impl.ClientSize;
FrameSize = impl.FrameSize;
-
+
this.GetObservable(PointerOverElementProperty)
.Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty())
@@ -214,9 +214,9 @@ namespace Avalonia.Controls
_pointerOverPreProcessor = new PointerOverPreProcessor(this);
_pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
- if(impl is ITopLevelWithSystemNavigationManager topLevelWithSystemNavigation)
+ if(impl.TryGetFeature() is {} systemNavigationManager)
{
- topLevelWithSystemNavigation.SystemNavigationManager.BackRequested += (s, e) =>
+ systemNavigationManager.BackRequested += (s, e) =>
{
e.RoutedEvent = BackRequestedEvent;
RaiseEvent(e);
@@ -333,8 +333,17 @@ namespace Avalonia.Controls
{
get
{
- if (_layoutManager == null)
+ if (_layoutManager is null)
+ {
_layoutManager = CreateLayoutManager();
+
+ if (_layoutManager is LayoutManager typedLayoutManager && Renderer is not null)
+ {
+ _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager);
+ _layoutDiagnosticBridge.SetupBridge();
+ }
+ }
+
return _layoutManager;
}
}
@@ -387,15 +396,9 @@ namespace Avalonia.Controls
public IStorageProvider StorageProvider => _storageProvider
??= AvaloniaLocator.Current.GetService()?.CreateProvider(this)
- ?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider
+ ?? PlatformImpl?.TryGetFeature()
?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
- ///
- void IRenderRoot.Invalidate(Rect rect)
- {
- PlatformImpl?.Invalidate(rect);
- }
-
///
Point IRenderRoot.PointToClient(PixelPoint p)
{
@@ -468,6 +471,9 @@ namespace Avalonia.Controls
Renderer?.Dispose();
Renderer = null!;
+ _layoutDiagnosticBridge?.Dispose();
+ _layoutDiagnosticBridge = null;
+
_pointerOverPreProcessor?.OnCompleted();
_pointerOverPreProcessorSubscription?.Dispose();
_backGestureSubscription?.Dispose();
@@ -654,7 +660,50 @@ namespace Avalonia.Controls
// Do nothing becuase TopLevel should't apply MirrorTransform on himself.
}
- ITextInputMethodImpl? ITextInputMethodRoot.InputMethod =>
- (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod;
+ ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => PlatformImpl?.TryGetFeature();
+
+ ///
+ /// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
+ ///
+ private sealed class LayoutDiagnosticBridge : IDisposable
+ {
+ private readonly RendererDiagnostics _diagnostics;
+ private readonly LayoutManager _layoutManager;
+ private bool _isHandling;
+
+ public LayoutDiagnosticBridge(RendererDiagnostics diagnostics, LayoutManager layoutManager)
+ {
+ _diagnostics = diagnostics;
+ _layoutManager = layoutManager;
+
+ diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
+ }
+
+ public void SetupBridge()
+ {
+ var needsHandling = (_diagnostics.DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0;
+ if (needsHandling != _isHandling)
+ {
+ _isHandling = needsHandling;
+ _layoutManager.LayoutPassTimed = needsHandling
+ ? timing => _diagnostics.LastLayoutPassTiming = timing
+ : null;
+ }
+ }
+
+ private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(RendererDiagnostics.DebugOverlays))
+ {
+ SetupBridge();
+ }
+ }
+
+ public void Dispose()
+ {
+ _diagnostics.PropertyChanged -= OnDiagnosticsPropertyChanged;
+ _layoutManager.LayoutPassTimed = null;
+ }
+ }
}
}
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index 020e09526e..2da8f38ea9 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -11,7 +11,7 @@ using Avalonia.Threading;
namespace Avalonia.DesignerSupport.Remote
{
- class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, ITopLevelImplWithStorageProvider
+ class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl
{
private readonly IAvaloniaRemoteTransportConnection _transport;
@@ -92,8 +92,16 @@ namespace Avalonia.DesignerSupport.Remote
public bool NeedsManagedDecorations => false;
- public IStorageProvider StorageProvider => new NoopStorageProvider();
-
+ public override object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(IStorageProvider))
+ {
+ return new NoopStorageProvider();
+ }
+
+ return base.TryGetFeature(featureType);
+ }
+
public void Activate()
{
}
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index 72d03c4b7f..ea427e4c92 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -14,6 +14,8 @@ using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Threading;
namespace Avalonia.DesignerSupport.Remote
{
@@ -61,9 +63,15 @@ namespace Avalonia.DesignerSupport.Remote
}));
}
- public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root, () =>
- new PlatformRenderInterfaceContextManager(null)
- .CreateRenderTarget(Surfaces));
+ class DummyRenderTimer : IRenderTimer
+ {
+ public event Action Tick;
+ public bool RunsInBackground => false;
+ }
+
+ public IRenderer CreateRenderer(IRenderRoot root) =>
+ new CompositingRenderer(root,
+ new Compositor(new RenderLoop(new DummyRenderTimer(), Dispatcher.UIThread), null), () => Surfaces);
public void Dispose()
{
@@ -186,6 +194,7 @@ namespace Avalonia.DesignerSupport.Remote
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+ public object TryGetFeature(Type featureType) => null;
}
class ClipboardStub : IClipboard
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs b/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs
index 9091b83ba2..540f2b549d 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs
@@ -1,12 +1,20 @@
using System;
+using System.IO;
namespace Avalonia.Diagnostics
{
internal static class Conventions
{
- public static string DefaultScreenshotsRoot =>
- System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),
- "Screenshots");
+ public static string DefaultScreenshotsRoot
+ {
+ get
+ {
+ var dir = System.IO.Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Screenshots");
+ Directory.CreateDirectory(dir);
+ return dir;
+ }
+ }
public static IScreenshotHandler DefaultScreenshotHandler { get; } =
new Screenshots.FilePickerHandler();
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index 3870cad7c5..3adad38ac6 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -1,11 +1,13 @@
using System;
using System.ComponentModel;
+using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.Reactive;
+using Avalonia.Rendering;
namespace Avalonia.Diagnostics.ViewModels
{
@@ -21,8 +23,6 @@ namespace Avalonia.Diagnostics.ViewModels
private string? _focusedControl;
private IInputElement? _pointerOverElement;
private bool _shouldVisualizeMarginPadding = true;
- private bool _shouldVisualizeDirtyRects;
- private bool _showFpsOverlay;
private bool _freezePopups;
private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot;
@@ -75,69 +75,76 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value);
}
- public bool ShouldVisualizeDirtyRects
+ public void ToggleVisualizeMarginPadding()
+ => ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding;
+
+ private IRenderer? TryGetRenderer()
+ => _root switch
+ {
+ TopLevel topLevel => topLevel.Renderer,
+ Controls.Application app => app.RendererRoot,
+ _ => null
+ };
+
+ private bool GetDebugOverlay(RendererDebugOverlays overlay)
+ => ((TryGetRenderer()?.Diagnostics.DebugOverlays ?? RendererDebugOverlays.None) & overlay) != 0;
+
+ private void SetDebugOverlay(RendererDebugOverlays overlay, bool enable,
+ [CallerMemberName] string? propertyName = null)
{
- get => _shouldVisualizeDirtyRects;
- set
+ if (TryGetRenderer() is not { } renderer)
{
- var changed = true;
- if (_root is TopLevel topLevel && topLevel.Renderer is { })
- {
- topLevel.Renderer.DrawDirtyRects = value;
- }
- else if (_root is Controls.Application app && app.RendererRoot is { })
- {
- app.RendererRoot.DrawDirtyRects = value;
- }
- else
- {
- changed = false;
- }
- if (changed)
- {
- RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value);
- }
+ return;
}
- }
- public void ToggleVisualizeDirtyRects()
- {
- ShouldVisualizeDirtyRects = !ShouldVisualizeDirtyRects;
+ var oldValue = renderer.Diagnostics.DebugOverlays;
+ var newValue = enable ? oldValue | overlay : oldValue & ~overlay;
+
+ if (oldValue == newValue)
+ {
+ return;
+ }
+
+ renderer.Diagnostics.DebugOverlays = newValue;
+ RaisePropertyChanged(propertyName);
}
- public void ToggleVisualizeMarginPadding()
+ public bool ShowDirtyRectsOverlay
{
- ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding;
+ get => GetDebugOverlay(RendererDebugOverlays.DirtyRects);
+ set => SetDebugOverlay(RendererDebugOverlays.DirtyRects, value);
}
+ public void ToggleDirtyRectsOverlay()
+ => ShowDirtyRectsOverlay = !ShowDirtyRectsOverlay;
+
public bool ShowFpsOverlay
{
- get => _showFpsOverlay;
- set
- {
- var changed = true;
- if (_root is TopLevel topLevel && topLevel.Renderer is { })
- {
- topLevel.Renderer.DrawFps = value;
- }
- else if (_root is Controls.Application app && app.RendererRoot is { })
- {
- app.RendererRoot.DrawFps = value;
- }
- else
- {
- changed = false;
- }
- if(changed)
- RaiseAndSetIfChanged(ref _showFpsOverlay, value);
- }
+ get => GetDebugOverlay(RendererDebugOverlays.Fps);
+ set => SetDebugOverlay(RendererDebugOverlays.Fps, value);
}
public void ToggleFpsOverlay()
+ => ShowFpsOverlay = !ShowFpsOverlay;
+
+ public bool ShowLayoutTimeGraphOverlay
{
- ShowFpsOverlay = !ShowFpsOverlay;
+ get => GetDebugOverlay(RendererDebugOverlays.LayoutTimeGraph);
+ set => SetDebugOverlay(RendererDebugOverlays.LayoutTimeGraph, value);
}
+ public void ToggleLayoutTimeGraphOverlay()
+ => ShowLayoutTimeGraphOverlay = !ShowLayoutTimeGraphOverlay;
+
+ public bool ShowRenderTimeGraphOverlay
+ {
+ get => GetDebugOverlay(RendererDebugOverlays.RenderTimeGraph);
+ set => SetDebugOverlay(RendererDebugOverlays.RenderTimeGraph, value);
+ }
+
+ public void ToggleRenderTimeGraphOverlay()
+ => ShowRenderTimeGraphOverlay = !ShowRenderTimeGraphOverlay;
+
public ConsoleViewModel Console { get; }
public ViewModelBase? Content
@@ -254,10 +261,10 @@ namespace Avalonia.Diagnostics.ViewModels
_pointerOverSubscription.Dispose();
_logicalTree.Dispose();
_visualTree.Dispose();
- if (_root is TopLevel top)
+
+ if (TryGetRenderer() is { } renderer)
{
- top.Renderer.DrawDirtyRects = false;
- top.Renderer.DrawFps = false;
+ renderer.Diagnostics.DebugOverlays = RendererDebugOverlays.None;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs
index a2ee37c625..ec88db6664 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
}
- protected bool RaiseAndSetIfChanged([NotNullIfNotNull("value")] ref T field, T value, [CallerMemberName] string propertyName = null!)
+ protected bool RaiseAndSetIfChanged([NotNullIfNotNull("value")] ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (!EqualityComparer.Default.Equals(field, value))
{
@@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels
return false;
}
- protected void RaisePropertyChanged([CallerMemberName] string propertyName = null!)
+ protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
{
var e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e);
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs
index 56d8737d79..42db30020f 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs
@@ -40,15 +40,14 @@ namespace Avalonia.Diagnostics.Views
_layoutRoot = this.GetControl("LayoutRoot");
- void SubscribeToBounds(Visual visual)
+ Visual? visual = _contentArea;
+ while (visual != null && !ReferenceEquals(visual, this))
{
- visual.GetPropertyChangedObservable(TransformedBoundsProperty)
+ visual.GetPropertyChangedObservable(BoundsProperty)
.Subscribe(UpdateSizeGuidelines);
+ visual = visual.VisualParent;
}
-
- SubscribeToBounds(_borderArea);
- SubscribeToBounds(_paddingArea);
- SubscribeToBounds(_contentArea);
+
}
private void InitializeComponent()
@@ -56,22 +55,27 @@ namespace Avalonia.Diagnostics.Views
AvaloniaXamlLoader.Load(this);
}
- private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs e)
+ private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs _)
{
void UpdateGuidelines(Visual area)
{
- if (area.TransformedBounds is TransformedBounds bounds)
+ // That's what TransformedBounds.Bounds actually was.
+ // The code below doesn't really make sense to me, so I've just changed v.TransformedBounds.Bounds
+ // to GetPseudoTransformedBounds
+ Rect GetPseudoTransformedBounds(Visual v) => new(v.Bounds.Size);
+ var bounds = GetPseudoTransformedBounds(area);
+
{
// Horizontal guideline
{
- var sizeArea = TranslateToRoot((_horizontalSize.TransformedBounds ?? default).Bounds.BottomLeft,
+ var sizeArea = TranslateToRoot(GetPseudoTransformedBounds(_horizontalSize).BottomLeft,
_horizontalSize);
- var start = TranslateToRoot(bounds.Bounds.BottomLeft, area);
+ var start = TranslateToRoot(bounds.BottomLeft, area);
SetPosition(_horizontalSizeBegin, start);
- var end = TranslateToRoot(bounds.Bounds.BottomRight, area);
+ var end = TranslateToRoot(bounds.BottomRight, area);
SetPosition(_horizontalSizeEnd, end.WithX(end.X - 1));
@@ -83,13 +87,13 @@ namespace Avalonia.Diagnostics.Views
// Vertical guideline
{
- var sizeArea = TranslateToRoot((_verticalSize.TransformedBounds ?? default).Bounds.TopRight, _verticalSize);
+ var sizeArea = TranslateToRoot(GetPseudoTransformedBounds(_verticalSize).TopRight, _verticalSize);
- var start = TranslateToRoot(bounds.Bounds.TopRight, area);
+ var start = TranslateToRoot(bounds.TopRight, area);
SetPosition(_verticalSizeBegin, start);
- var end = TranslateToRoot(bounds.Bounds.BottomRight, area);
+ var end = TranslateToRoot(bounds.BottomRight, area);
SetPosition(_verticalSizeEnd, end.WithY(end.Y - 1));
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index 97e21079c1..eac807a5bc 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -65,28 +65,42 @@
-
public class AvaloniaNativePlatformOptions
{
- ///
- /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
- ///
- ///
- /// Avalonia has two rendering modes: Immediate and Deferred rendering.
- /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements.
- ///
- public bool UseDeferredRendering { get; set; } = true;
-
- ///
- /// Enables new compositing rendering with UWP-like API
- ///
- public bool UseCompositor { get; set; } = true;
-
///
/// Determines whether to use GPU for rendering in your project. The default value is true.
///
diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs
index 4e0c037154..c390459286 100644
--- a/src/Avalonia.Native/DeferredFramebuffer.cs
+++ b/src/Avalonia.Native/DeferredFramebuffer.cs
@@ -63,7 +63,7 @@ namespace Avalonia.Native
},
Width = Size.Width,
Height = Size.Height,
- PixelFormat = (AvnPixelFormat)Format,
+ PixelFormat = (AvnPixelFormat)Format.FormatEnum,
Stride = RowBytes
};
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index 880a385744..f27d94b61a 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -11,14 +11,14 @@ using Avalonia.Platform.Interop;
namespace Avalonia.Native
{
- internal class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter
+ internal class WindowImpl : WindowBaseImpl, IWindowImpl
{
private readonly AvaloniaNativePlatformOptions _opts;
private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
private DoubleClickHelper _doubleClickHelper;
-
+ private readonly ITopLevelNativeMenuExporter _nativeMenuExporter;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
@@ -32,7 +32,7 @@ namespace Avalonia.Native
Init(_native = factory.CreateWindow(e, glFeature.SharedContext.Context), factory.CreateScreens());
}
- NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
+ _nativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
}
class WindowEvents : WindowBaseEvents, IAvnWindowEvents
@@ -209,8 +209,6 @@ namespace Avalonia.Native
public Func Closing { get; set; }
- public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
-
public void Move(PixelPoint point) => Position = point;
public override IPopupImpl CreatePopup() =>
@@ -227,5 +225,15 @@ namespace Avalonia.Native
{
_native.SetEnabled(enable.AsComBool());
}
+
+ public override object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(ITopLevelNativeMenuExporter))
+ {
+ return _nativeMenuExporter;
+ }
+
+ return base.TryGetFeature(featureType);
+ }
}
}
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 7686b42275..1f290acd86 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -47,13 +47,12 @@ namespace Avalonia.Native
}
internal abstract class WindowBaseImpl : IWindowBaseImpl,
- IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
+ IFramebufferPlatformSurface
{
protected readonly IAvaloniaNativeFactory _factory;
protected IInputRoot _inputRoot;
IAvnWindowBase _native;
private object _syncRoot = new object();
- private bool _deferredRendering = false;
private bool _gpu = false;
private readonly MouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
@@ -63,18 +62,17 @@ namespace Avalonia.Native
private double _savedScaling;
private GlPlatformSurface _glSurface;
private NativeControlHostImpl _nativeControlHost;
+ private IStorageProvider _storageProvider;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature)
{
_factory = factory;
_gpu = opts.UseGpu && glFeature != null;
- _deferredRendering = opts.UseDeferredRendering;
_keyboard = AvaloniaLocator.Current.GetService();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService();
- StorageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
}
protected void Init(IAvnWindowBase window, IAvnScreens screens)
@@ -89,6 +87,7 @@ namespace Avalonia.Native
_savedLogicalSize = ClientSize;
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
+ _storageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
@@ -365,25 +364,10 @@ namespace Avalonia.Native
public IRenderer CreateRenderer(IRenderRoot root)
{
- var customRendererFactory = AvaloniaLocator.Current.GetService();
- var loop = AvaloniaLocator.Current.GetService();
- if (customRendererFactory != null)
- return customRendererFactory.Create(root, loop);
-
- if (_deferredRendering)
+ return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces)
{
- if (AvaloniaNativePlatform.Compositor != null)
- return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces)
- {
- RenderOnlyOnRenderThread = false
- };
- return new DeferredRenderer(root, loop,
- () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces),
- AvaloniaNativePlatform.RenderInterface);
- }
-
- return new ImmediateRenderer((Visual)root,
- () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), AvaloniaNativePlatform.RenderInterface);
+ RenderOnlyOnRenderThread = false
+ };
}
public virtual void Dispose()
@@ -525,9 +509,21 @@ namespace Avalonia.Native
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
+ public virtual object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(INativeControlHostImpl))
+ {
+ return _nativeControlHost;
+ }
+
+ if (featureType == typeof(IStorageProvider))
+ {
+ return _storageProvider;
+ }
- public IPlatformHandle Handle { get; private set; }
+ return null;
+ }
- public IStorageProvider StorageProvider { get; }
+ public IPlatformHandle Handle { get; private set; }
}
}
diff --git a/src/Avalonia.X11/X11ImmediateRendererProxy.cs b/src/Avalonia.X11/X11ImmediateRendererProxy.cs
deleted file mode 100644
index ef061cbe5c..0000000000
--- a/src/Avalonia.X11/X11ImmediateRendererProxy.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Threading;
-using Avalonia.VisualTree;
-
-namespace Avalonia.X11
-{
- public class X11ImmediateRendererProxy : IRenderer, IRenderLoopTask
- {
- private readonly IRenderLoop _loop;
- private ImmediateRenderer _renderer;
- private bool _invalidated;
- private bool _running;
- private object _lock = new object();
-
- public X11ImmediateRendererProxy(Visual root, IRenderLoop loop, Func renderTargetFactory,
- PlatformRenderInterfaceContextManager renderContext)
- {
- _loop = loop;
- _renderer = new ImmediateRenderer(root, renderTargetFactory, renderContext);
- }
-
- public void Dispose()
- {
- _running = false;
- _renderer.Dispose();
- }
-
- public bool DrawFps
- {
- get => _renderer.DrawFps;
- set => _renderer.DrawFps = value;
- }
-
- public bool DrawDirtyRects
- {
- get => _renderer.DrawDirtyRects;
- set => _renderer.DrawDirtyRects = value;
- }
-
- public event EventHandler SceneInvalidated
- {
- add => _renderer.SceneInvalidated += value;
- remove => _renderer.SceneInvalidated -= value;
- }
-
- public void AddDirty(Visual visual)
- {
- lock (_lock)
- _invalidated = true;
- _renderer.AddDirty(visual);
- }
-
- public IEnumerable HitTest(Point p, Visual root, Func filter)
- {
- return _renderer.HitTest(p, root, filter);
- }
-
- public Visual HitTestFirst(Point p, Visual root, Func filter)
- {
- return _renderer.HitTestFirst(p, root, filter);
- }
-
- public void RecalculateChildren(Visual visual)
- {
- _renderer.RecalculateChildren(visual);
- }
-
- public void Resized(Size size)
- {
- _renderer.Resized(size);
- }
-
- public void Paint(Rect rect)
- {
- _invalidated = false;
- _renderer.Paint(rect);
- }
-
- public void Start()
- {
- _running = true;
- _loop.Add(this);
- _renderer.Start();
- }
-
- public void Stop()
- {
- _running = false;
- _loop.Remove(this);
- _renderer.Stop();
- }
-
- public ValueTask TryGetRenderInterfaceFeature(Type featureType) =>
- _renderer.TryGetRenderInterfaceFeature(featureType);
-
- public bool NeedsUpdate => false;
- public void Update(TimeSpan time)
- {
-
- }
-
- public void Render()
- {
- if (_invalidated)
- {
- lock (_lock)
- _invalidated = false;
- Dispatcher.UIThread.Post(() =>
- {
- if (_running)
- Paint(new Rect(0, 0, 100000, 100000));
- });
- }
- }
- }
-}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 4cc2cca39d..c5ae327c2f 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -30,7 +30,6 @@ namespace Avalonia.X11
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public Compositor Compositor { get; private set; }
- public PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public IntPtr OrphanedWindow { get; private set; }
@@ -103,12 +102,8 @@ namespace Avalonia.X11
}
var gl = AvaloniaLocator.Current.GetService();
-
- if (options.UseCompositor)
- Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl);
- else
- RenderInterface = new(gl);
+ Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl);
}
public IntPtr DeferredDisplay { get; set; }
@@ -226,18 +221,7 @@ namespace Avalonia
/// The default value is true.
///
public bool UseDBusFilePicker { get; set; } = true;
-
- ///
- /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
- ///
- ///
- /// Avalonia has two rendering modes: Immediate and Deferred rendering.
- /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements.
- ///
- public bool UseDeferredRendering { get; set; } = true;
-
- public bool UseCompositor { get; set; } = true;
-
+
///
/// Determines whether to use IME.
/// IME would be enabled by default if the current user input language is one of the following: Mandarin, Japanese, Vietnamese or Korean.
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 84331aa43a..c78ef2350e 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -27,11 +27,7 @@ using static Avalonia.X11.XLib;
// ReSharper disable StringLiteralTypo
namespace Avalonia.X11
{
- unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client,
- ITopLevelImplWithNativeMenuExporter,
- ITopLevelImplWithNativeControlHost,
- ITopLevelImplWithTextInputMethod,
- ITopLevelImplWithStorageProvider
+ unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client
{
private readonly AvaloniaX11Platform _platform;
private readonly bool _popup;
@@ -43,6 +39,9 @@ namespace Avalonia.X11
private readonly MouseDevice _mouse;
private readonly TouchDevice _touch;
private readonly IKeyboardDevice _keyboard;
+ private readonly ITopLevelNativeMenuExporter _nativeMenuExporter;
+ private readonly IStorageProvider _storageProvider;
+ private readonly X11NativeControlHost _nativeControlHost;
private PixelPoint? _position;
private PixelSize _realSize;
private IntPtr _handle;
@@ -199,8 +198,8 @@ namespace Avalonia.X11
if(_popup)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
if (platform.Options.UseDBusMenu)
- NativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle);
- NativeControlHost = new X11NativeControlHost(_platform, this);
+ _nativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle);
+ _nativeControlHost = new X11NativeControlHost(_platform, this);
InitializeIme();
XChangeProperty(_x11.Display, _handle, _x11.Atoms.WM_PROTOCOLS, _x11.Atoms.XA_ATOM, 32,
@@ -213,7 +212,7 @@ namespace Avalonia.X11
_x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1);
}
- StorageProvider = new CompositeStorageProvider(new Func>[]
+ _storageProvider = new CompositeStorageProvider(new Func>[]
{
() => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreate(Handle) : Task.FromResult(null),
() => GtkSystemDialog.TryCreate(this),
@@ -384,25 +383,8 @@ namespace Avalonia.X11
public Action PositionChanged { get; set; }
public Action LostFocus { get; set; }
- public IRenderer CreateRenderer(IRenderRoot root)
- {
- var loop = AvaloniaLocator.Current.GetService();
- var customRendererFactory = AvaloniaLocator.Current.GetService();
-
- if (customRendererFactory != null)
- return customRendererFactory.Create(root, loop);
-
- return _platform.Options.UseDeferredRendering
- ? _platform.Options.UseCompositor
- ? new CompositingRenderer(root, this._platform.Compositor, () => Surfaces)
- : new DeferredRenderer(root, loop, () => _platform.RenderInterface.CreateRenderTarget(Surfaces), _platform.RenderInterface)
- {
- RenderOnlyOnRenderThread = true
- }
- : new X11ImmediateRendererProxy((Visual)root, loop,
- () => _platform.RenderInterface.CreateRenderTarget(Surfaces),
- _platform.RenderInterface);
- }
+ public IRenderer CreateRenderer(IRenderRoot root) =>
+ new CompositingRenderer(root, _platform.Compositor, () => Surfaces);
void OnEvent(ref XEvent ev)
{
@@ -813,6 +795,31 @@ namespace Avalonia.X11
Cleanup();
}
+ public virtual object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(ITopLevelNativeMenuExporter))
+ {
+ return _nativeMenuExporter;
+ }
+
+ if (featureType == typeof(IStorageProvider))
+ {
+ return _storageProvider;
+ }
+
+ if (featureType == typeof(ITextInputMethodImpl))
+ {
+ return _ime;
+ }
+
+ if (featureType == typeof(INativeControlHostImpl))
+ {
+ return _nativeControlHost;
+ }
+
+ return null;
+ }
+
void Cleanup()
{
if (_rawEventGrouper != null)
@@ -1212,9 +1219,6 @@ namespace Avalonia.X11
}
public IPopupPositioner PopupPositioner { get; }
- public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
- public INativeControlHostImpl NativeControlHost { get; }
- public ITextInputMethodImpl TextInputMethod => _ime;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
_transparencyHelper?.SetTransparencyRequest(transparencyLevel);
@@ -1232,8 +1236,6 @@ namespace Avalonia.X11
public bool NeedsManagedDecorations => false;
- public IStorageProvider StorageProvider { get; }
-
public class SurfacePlatformHandle : IPlatformNativeSurfaceHandle
{
private readonly X11Window _owner;
diff --git a/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs b/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs
index cc7d8c3af1..bc38067f4a 100644
--- a/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs
+++ b/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs
@@ -5,11 +5,11 @@ using Avalonia.Platform;
namespace Avalonia.Browser
{
- internal class BrowserSystemNavigationManager : ISystemNavigationManager
+ internal class BrowserSystemNavigationManagerImpl : ISystemNavigationManagerImpl
{
public event EventHandler? BackRequested;
- public BrowserSystemNavigationManager()
+ public BrowserSystemNavigationManagerImpl()
{
NavigationHelper.AddBackHandler(() =>
{
diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
index 3affcfb4c2..f1cd441f45 100644
--- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
+++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
@@ -19,8 +19,7 @@ using Avalonia.Rendering.Composition;
namespace Avalonia.Browser
{
- internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
- ITopLevelWithSystemNavigationManager
+ internal class BrowserTopLevelImpl : ITopLevelImpl
{
private Size _clientSize;
private IInputRoot? _inputRoot;
@@ -29,6 +28,9 @@ namespace Avalonia.Browser
private readonly TouchDevice _touchDevice;
private readonly PenDevice _penDevice;
private string _currentCursor = CssCursor.Default;
+ private readonly INativeControlHostImpl _nativeControlHost;
+ private readonly IStorageProvider _storageProvider;
+ private readonly ISystemNavigationManagerImpl _systemNavigationManager;
public BrowserTopLevelImpl(AvaloniaView avaloniaView)
{
@@ -38,7 +40,9 @@ namespace Avalonia.Browser
AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
_touchDevice = new TouchDevice();
_penDevice = new PenDevice();
- NativeControlHost = _avaloniaView.GetNativeControlHostImpl();
+ _nativeControlHost = _avaloniaView.GetNativeControlHostImpl();
+ _storageProvider = new BrowserStorageProvider();
+ _systemNavigationManager = new BrowserSystemNavigationManagerImpl();
}
public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
@@ -236,11 +240,29 @@ namespace Avalonia.Browser
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
- public ITextInputMethodImpl TextInputMethod => _avaloniaView;
+ public object? TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(IStorageProvider))
+ {
+ return _storageProvider;
+ }
- public INativeControlHostImpl? NativeControlHost { get; }
- public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider();
+ if (featureType == typeof(ITextInputMethodImpl))
+ {
+ return _avaloniaView;
+ }
- public ISystemNavigationManager SystemNavigationManager { get; } = new BrowserSystemNavigationManager();
+ if (featureType == typeof(ISystemNavigationManagerImpl))
+ {
+ return _systemNavigationManager;
+ }
+
+ if (featureType == typeof(INativeControlHostImpl))
+ {
+ return _nativeControlHost;
+ }
+
+ return null;
+ }
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index cb158097eb..9dc7f08064 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -24,16 +24,12 @@ namespace Avalonia.LinuxFramebuffer
_inputBackend = inputBackend;
Surfaces = new object[] { _outputBackend };
-
- Invalidate(default);
_inputBackend.Initialize(this, e => Input?.Invoke(e));
}
public IRenderer CreateRenderer(IRenderRoot root)
{
- var factory = AvaloniaLocator.Current.GetService();
- var renderLoop = AvaloniaLocator.Current.GetService();
- return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces);
+ return new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces);
}
public void Dispose()
@@ -41,11 +37,6 @@ namespace Avalonia.LinuxFramebuffer
throw new NotSupportedException();
}
-
- public void Invalidate(Rect rect)
- {
- }
-
public void SetInputRoot(IInputRoot inputRoot)
{
InputRoot = inputRoot;
@@ -86,5 +77,6 @@ namespace Avalonia.LinuxFramebuffer
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+ public object TryGetFeature(Type featureType) => null;
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index 4202ba821f..2dcce12df9 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -26,8 +26,8 @@ namespace Avalonia.LinuxFramebuffer
private static readonly Stopwatch St = Stopwatch.StartNew();
internal static uint Timestamp => (uint)St.ElapsedTicks;
public static InternalPlatformThreadingInterface? Threading;
-
- internal static Compositor? Compositor { get; private set; }
+
+ internal static Compositor Compositor { get; private set; } = null!;
LinuxFramebufferPlatform(IOutputBackend backend)
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
index f3f9a12ac8..56c6bdb8c4 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
@@ -93,9 +93,8 @@ namespace Avalonia.LinuxFramebuffer
void SetBpp(PixelFormat format)
{
- switch (format)
+ if (format == PixelFormat.Rgba8888)
{
- case PixelFormat.Rgba8888:
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
@@ -105,8 +104,9 @@ namespace Avalonia.LinuxFramebuffer
_varInfo.green.offset = 8;
_varInfo.blue.offset = 16;
_varInfo.transp.offset = 24;
- break;
- case PixelFormat.Bgra8888:
+ }
+ else if (format == PixelFormat.Bgra8888)
+ {
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
@@ -116,8 +116,9 @@ namespace Avalonia.LinuxFramebuffer
_varInfo.green.offset = 8;
_varInfo.red.offset = 16;
_varInfo.transp.offset = 24;
- break;
- case PixelFormat.Rgb565:
+ }
+ else if (format == PixelFormat.Rgb565)
+ {
_varInfo.bits_per_pixel = 16;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
@@ -126,8 +127,8 @@ namespace Avalonia.LinuxFramebuffer
_varInfo.green.length = 6;
_varInfo.blue.offset = 11;
_varInfo.blue.length = 5;
- break;
}
+ else throw new NotSupportedException($"Pixel format {format} is not supported");
}
public string Id { get; private set; }
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index 574d46e737..a24d4eb6e9 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -68,29 +68,44 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// If there is no x:DataType directive,
// do more specialized inference
- if (directiveDataContextTypeNode is null)
+ if (directiveDataContextTypeNode is null && inferredDataContextTypeNode is null)
{
- if (context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType())
- && inferredDataContextTypeNode is null)
+ // Infer data type from collection binding on a control that displays items.
+ var property = context.ParentNodes().OfType().FirstOrDefault();
+ var attributeType = context.GetAvaloniaTypes().InheritDataTypeFromItemsAttribute;
+ var attribute = property?.Property?.GetClrProperty().CustomAttributes
+ .FirstOrDefault(a => a.Type == attributeType);
+
+ if (attribute is not null)
{
- // Infer data type from collection binding on a control that displays items.
- var parentObject = context.ParentNodes().OfType().FirstOrDefault();
- if (parentObject != null)
+ var propertyName = (string)attribute.Parameters.First();
+ XamlAstConstructableObjectNode parentObject;
+ if (attribute.Properties.TryGetValue("AncestorType", out var type)
+ && type is IXamlType xamlType)
{
- var parentType = parentObject.Type.GetClrType();
-
- if (context.GetAvaloniaTypes().ItemsControl.IsDirectlyAssignableFrom(parentType)
- || context.GetAvaloniaTypes().ItemsRepeater.IsDirectlyAssignableFrom(parentType))
- {
- inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject);
- }
+ parentObject = context.ParentNodes().OfType()
+ .FirstOrDefault(n => n.Type.GetClrType().FullName == xamlType.FullName);
}
-
- if (inferredDataContextTypeNode is null)
+ else
+ {
+ parentObject = context.ParentNodes().OfType().FirstOrDefault();
+ }
+
+ if (parentObject != null)
{
- inferredDataContextTypeNode = new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject, propertyName);
}
}
+
+ if (inferredDataContextTypeNode is null
+ // Only for IDataTemplate, as we want to notify user as early as possible,
+ // and IDataTemplate cannot inherit DataType from the parent implicitly.
+ && context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType()))
+ {
+ // We can't infer the collection type and the currently calculated type is definitely wrong.
+ // Notify the user that we were unable to infer the data context type if they use a compiled binding.
+ inferredDataContextTypeNode = new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ }
}
return directiveDataContextTypeNode ?? inferredDataContextTypeNode ?? node;
@@ -98,18 +113,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
-
- private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode parentObject)
+
+ private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(
+ AstTransformationContext context, XamlAstConstructableObjectNode on,
+ XamlAstConstructableObjectNode parentObject, string propertyName)
{
var parentItemsValue = parentObject
.Children.OfType()
- .FirstOrDefault(pa => pa.Property.Name == "Items")
+ .FirstOrDefault(pa => pa.Property.Name == propertyName)
?.Values[0];
if (parentItemsValue is null)
{
- // We can't infer the collection type and the currently calculated type is definitely wrong.
- // Notify the user that we were unable to infer the data context type if they use a compiled binding.
- return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ return null;
}
IXamlType itemsCollectionType = null;
@@ -140,9 +155,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
}
- // We can't infer the collection type and the currently calculated type is definitely wrong.
- // Notify the user that we were unable to infer the data context type if they use a compiled binding.
- return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+
+ return null;
}
private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj)
@@ -208,6 +222,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
}
- public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element.", Value);
+ public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element. Please set x:DataType on the Binding or parent.", Value);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 19734a2226..a4a3bcce94 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -30,6 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType AssignBindingAttribute { get; }
public IXamlType DependsOnAttribute { get; }
public IXamlType DataTypeAttribute { get; }
+ public IXamlType InheritDataTypeFromItemsAttribute { get; }
public IXamlType MarkupExtensionOptionAttribute { get; }
public IXamlType MarkupExtensionDefaultOptionAttribute { get; }
public IXamlType OnExtensionType { get; }
@@ -137,6 +138,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute");
DataTypeAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DataTypeAttribute");
+ InheritDataTypeFromItemsAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromItemsAttribute");
MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute");
MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute");
OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On");
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
index ae29dcf9cb..9c7b6deb7c 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
@@ -37,6 +37,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
bindingResultType = transformed.BindingResultType;
binding.Arguments[0] = transformed;
}
+ else if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlIlBindingPathNode alreadyTransformed)
+ {
+ bindingResultType = alreadyTransformed.BindingResultType;
+ }
else
{
var bindingPathAssignment = binding.Children.OfType()
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index 802736119f..4ab873fd8d 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -10,9 +10,10 @@ namespace Avalonia.Skia
///
/// Immutable Skia bitmap.
///
- internal class ImmutableBitmap : IDrawableBitmapImpl
+ internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
{
private readonly SKImage _image;
+ private readonly SKBitmap? _bitmap;
///
/// Create immutable bitmap from given stream.
@@ -23,12 +24,13 @@ namespace Avalonia.Skia
using (var skiaStream = new SKManagedStream(stream))
{
using (var data = SKData.Create(skiaStream))
- _image = SKImage.FromEncodedData(data);
-
- if (_image == null)
- {
+ _bitmap = SKBitmap.Decode(data);
+
+ if (_bitmap == null)
throw new ArgumentException("Unable to load bitmap from provided data");
- }
+
+ _bitmap.SetImmutable();
+ _image = SKImage.FromBitmap(_bitmap);
PixelSize = new PixelSize(_image.Width, _image.Height);
@@ -47,10 +49,10 @@ namespace Avalonia.Skia
public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
{
SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);
- SKImage output = SKImage.Create(info);
- src._image.ScalePixels(output.PeekPixels(), interpolationMode.ToSKFilterQuality());
-
- _image = output;
+ _bitmap = new SKBitmap(info);
+ src._image.ScalePixels(_bitmap.PeekPixels(), interpolationMode.ToSKFilterQuality());
+ _bitmap.SetImmutable();
+ _image = SKImage.FromBitmap(_bitmap);
PixelSize = new PixelSize(_image.Width, _image.Height);
@@ -71,8 +73,11 @@ namespace Avalonia.Skia
// decode the bitmap at the nearest size
var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height);
- var bmp = SKBitmap.Decode(codec, nearest);
+ _bitmap = SKBitmap.Decode(codec, nearest);
+ if (_bitmap == null)
+ throw new ArgumentException("Unable to load bitmap from provided data");
+
// now scale that to the size that we want
var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height);
@@ -88,15 +93,16 @@ namespace Avalonia.Skia
desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize);
}
- if (bmp.Width != desired.Width || bmp.Height != desired.Height)
+ if (_bitmap.Width != desired.Width || _bitmap.Height != desired.Height)
{
- var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality());
- bmp.Dispose();
- bmp = scaledBmp;
+ var scaledBmp = _bitmap.Resize(desired, interpolationMode.ToSKFilterQuality());
+ _bitmap.Dispose();
+ _bitmap = scaledBmp;
}
+
+ _bitmap!.SetImmutable();
- _image = SKImage.FromBitmap(bmp);
- bmp.Dispose();
+ _image = SKImage.FromBitmap(_bitmap);
if (_image == null)
{
@@ -121,9 +127,15 @@ namespace Avalonia.Skia
/// Data pixels.
public ImmutableBitmap(PixelSize size, Vector dpi, int stride, PixelFormat format, AlphaFormat alphaFormat, IntPtr data)
{
- var imageInfo = new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), alphaFormat.ToSkAlphaType());
-
- _image = SKImage.FromPixelCopy(imageInfo, data, stride);
+ using (var tmp = new SKBitmap())
+ {
+ tmp.InstallPixels(
+ new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), alphaFormat.ToSkAlphaType()),
+ data);
+ _bitmap = tmp.Copy();
+ }
+ _bitmap!.SetImmutable();
+ _image = SKImage.FromBitmap(_bitmap);
if (_image == null)
{
@@ -143,6 +155,7 @@ namespace Avalonia.Skia
public void Dispose()
{
_image.Dispose();
+ _bitmap?.Dispose();
}
///
@@ -162,5 +175,14 @@ namespace Avalonia.Skia
{
context.Canvas.DrawImage(_image, sourceRect, destRect, paint);
}
+
+ public PixelFormat? Format => _bitmap?.ColorType.ToAvalonia();
+ public ILockedFramebuffer Lock()
+ {
+ if (_bitmap == null)
+ throw new NotSupportedException();
+ return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi,
+ _bitmap.ColorType.ToAvalonia().Value, null);
+ }
}
}
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index b4297a7c33..d12db39ad6 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -41,6 +41,11 @@ namespace Avalonia.Skia
public PixelFormat DefaultPixelFormat { get; }
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) =>
+ format == PixelFormats.Rgb565
+ || format == PixelFormats.Bgra8888
+ || format == PixelFormats.Rgba8888;
+
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index d584216f17..20dde27e9a 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -127,6 +127,17 @@ namespace Avalonia.Skia
throw new ArgumentException("Unknown pixel format: " + fmt);
}
+ public static PixelFormat? ToAvalonia(this SKColorType colorType)
+ {
+ if (colorType == SKColorType.Rgb565)
+ return PixelFormats.Rgb565;
+ if (colorType == SKColorType.Bgra8888)
+ return PixelFormats.Bgra8888;
+ if (colorType == SKColorType.Rgba8888)
+ return PixelFormats.Rgba8888;
+ return null;
+ }
+
public static PixelFormat ToPixelFormat(this SKColorType fmt)
{
if (fmt == SKColorType.Rgb565)
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index 9864a14a9c..56e627f2d8 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -154,6 +154,8 @@ namespace Avalonia.Skia
}
}
+ public PixelFormat? Format => _bitmap.ColorType.ToAvalonia();
+
///
public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap);
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index fbf8097ece..eb3f9911df 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -339,5 +339,8 @@ namespace Avalonia.Direct2D1
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Bgra8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) =>
+ format == PixelFormats.Bgra8888
+ || format == PixelFormats.Rgba8888;
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
index 051790ef03..72a48aca0c 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
@@ -1,11 +1,14 @@
using System;
using System.IO;
+using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Win32.Interop;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using AlphaFormat = Avalonia.Platform.AlphaFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using Avalonia.Metadata;
+using Avalonia.Platform;
+using PixelFormat = SharpDX.WIC.PixelFormat;
namespace Avalonia.Direct2D1.Media
{
@@ -13,7 +16,7 @@ namespace Avalonia.Direct2D1.Media
/// A WIC implementation of a .
///
[Unstable]
- public class WicBitmapImpl : BitmapImpl
+ public class WicBitmapImpl : BitmapImpl, IReadableBitmapImpl
{
private readonly BitmapDecoder _decoder;
@@ -197,5 +200,38 @@ namespace Avalonia.Direct2D1.Media
encoder.Commit();
}
}
+
+ class LockedBitmap : ILockedFramebuffer
+ {
+ private readonly WicBitmapImpl _parent;
+ private readonly BitmapLock _lock;
+ private readonly APixelFormat _format;
+
+ public LockedBitmap(WicBitmapImpl parent, BitmapLock l, APixelFormat format)
+ {
+ _parent = parent;
+ _lock = l;
+ _format = format;
+ }
+
+
+ public void Dispose()
+ {
+ _lock.Dispose();
+ _parent.Version++;
+ }
+
+ public IntPtr Address => _lock.Data.DataPointer;
+ public PixelSize Size => _lock.Size.ToAvalonia();
+ public int RowBytes => _lock.Stride;
+ public Vector Dpi => _parent.Dpi;
+ public APixelFormat Format => _format;
+
+ }
+
+ APixelFormat? IReadableBitmapImpl.Format => PixelFormat;
+
+ public ILockedFramebuffer Lock() =>
+ new LockedBitmap(this, WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value);
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
index 2e40bdd9d1..5f4c033cf7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
@@ -29,35 +29,6 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
}
- class LockedBitmap : ILockedFramebuffer
- {
- private readonly WriteableWicBitmapImpl _parent;
- private readonly BitmapLock _lock;
- private readonly PixelFormat _format;
-
- public LockedBitmap(WriteableWicBitmapImpl parent, BitmapLock l, PixelFormat format)
- {
- _parent = parent;
- _lock = l;
- _format = format;
- }
-
-
- public void Dispose()
- {
- _lock.Dispose();
- _parent.Version++;
- }
-
- public IntPtr Address => _lock.Data.DataPointer;
- public PixelSize Size => _lock.Size.ToAvalonia();
- public int RowBytes => _lock.Stride;
- public Vector Dpi => _parent.Dpi;
- public PixelFormat Format => _format;
-
- }
-
- public ILockedFramebuffer Lock() =>
- new LockedBitmap(this, WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value);
+ public PixelFormat? Format => PixelFormat;
}
}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 76fc4fa21d..aff533c443 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -261,5 +261,7 @@ namespace Avalonia.Win32.Interop.Wpf
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+
+ public object TryGetFeature(Type featureType) => null;
}
}
diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs
index 7f4b1c976d..8feecab4dd 100644
--- a/src/Windows/Avalonia.Win32/FramebufferManager.cs
+++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Win32
internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable
{
private const int _bytesPerPixel = 4;
- private const PixelFormat _format = PixelFormat.Bgra8888;
+ private static readonly PixelFormat s_format = PixelFormat.Bgra8888;
private readonly IntPtr _hwnd;
private readonly object _lock;
@@ -50,7 +50,7 @@ namespace Avalonia.Win32
return fb = new LockedFramebuffer(
framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes,
- GetCurrentDpi(), _format, _onDisposeAction);
+ GetCurrentDpi(), s_format, _onDisposeAction);
}
finally
{
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 7e1e00bded..f16a1ca8cf 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -42,17 +42,6 @@ namespace Avalonia
///
public class Win32PlatformOptions
{
- ///
- /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
- ///
- ///
- /// Avalonia has two rendering modes: Immediate and Deferred rendering.
- /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements.
- ///
- public bool UseDeferredRendering { get; set; } = true;
-
- public bool UseCompositor { get; set; } = true;
-
///
/// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false.
///
@@ -137,12 +126,10 @@ namespace Avalonia.Win32
///
public static Version WindowsVersion { get; } = RtlGetVersion();
- public static bool UseDeferredRendering => Options.UseDeferredRendering;
internal static bool UseOverlayPopups => Options.OverlayPopups;
public static Win32PlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
- internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@@ -181,11 +168,8 @@ namespace Avalonia.Win32
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind().ToSingleton();
-
- if (Options.UseCompositor)
- Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics);
- else
- RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics);
+
+ Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics);
}
public bool HasMessages()
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index a7741477e5..1e0d92d442 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -35,10 +35,7 @@ namespace Avalonia.Win32
/// Window implementation for Win32 platform.
///
[Unstable]
- public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
- ITopLevelImplWithNativeControlHost,
- ITopLevelImplWithTextInputMethod,
- ITopLevelImplWithStorageProvider
+ public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
{
private static readonly List s_instances = new List();
@@ -83,6 +80,7 @@ namespace Avalonia.Win32
private readonly bool _wmPointerEnabled;
private Win32NativeControlHost _nativeControlHost;
+ private IStorageProvider _storageProvider;
private WndProc _wndProcDelegate;
private string _className;
private IntPtr _hwnd;
@@ -183,7 +181,7 @@ namespace Avalonia.Win32
}
Screen = new ScreenImpl();
- StorageProvider = new Win32StorageProvider(this);
+ _storageProvider = new Win32StorageProvider(this);
_nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition);
s_instances.Add(this);
@@ -322,6 +320,26 @@ namespace Avalonia.Win32
private bool IsMouseInPointerEnabled => _wmPointerEnabled && IsMouseInPointerEnabled();
+ public object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(ITextInputMethodImpl))
+ {
+ return Imm32InputMethod.Current;
+ }
+
+ if (featureType == typeof(INativeControlHostImpl))
+ {
+ return _nativeControlHost;
+ }
+
+ if (featureType == typeof(IStorageProvider))
+ {
+ return _storageProvider;
+ }
+
+ return null;
+ }
+
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
TransparencyLevel = EnableBlur(transparencyLevel);
@@ -558,32 +576,8 @@ namespace Avalonia.Win32
_maxSize = maxSize;
}
- public IRenderer CreateRenderer(IRenderRoot root)
- {
- var loop = AvaloniaLocator.Current.GetService();
- var customRendererFactory = AvaloniaLocator.Current.GetService();
-
- if (customRendererFactory != null)
- return customRendererFactory.Create(root, loop);
-
- if (Win32Platform.Compositor != null)
- return new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces);
-
- return Win32Platform.UseDeferredRendering
- ? _isUsingComposition
- ? new DeferredRenderer(root, loop,
- () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces),
- Win32Platform.RenderInterface)
- {
- RenderOnlyOnRenderThread = true
- }
- : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock,
- renderTargetFactory: () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces),
- renderInterface: Win32Platform.RenderInterface)
- : new ImmediateRenderer((Visual)root,
- () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces),
- Win32Platform.RenderInterface);
- }
+ public IRenderer CreateRenderer(IRenderRoot root) =>
+ new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces);
public void Resize(Size value, PlatformResizeReason reason)
{
@@ -1489,10 +1483,6 @@ namespace Avalonia.Win32
public void Dispose() => _owner._resizeReason = _restore;
}
- public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current;
-
- public IStorageProvider StorageProvider { get; }
-
private class WindowImplPlatformHandle : IPlatformNativeSurfaceHandle
{
private readonly WindowImpl _owner;
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 7442861597..2d6b93f818 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -68,17 +68,18 @@ namespace Avalonia.iOS
settings?.TraitCollectionDidChange();
}
- internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost,
- ITopLevelImplWithStorageProvider
+ internal class TopLevelImpl : ITopLevelImpl
{
private readonly AvaloniaView _view;
+ private readonly INativeControlHostImpl _nativeControlHost;
+ private readonly IStorageProvider _storageProvider;
public AvaloniaView View => _view;
public TopLevelImpl(AvaloniaView view)
{
_view = view;
- NativeControlHost = new NativeControlHostImpl(_view);
- StorageProvider = new IOSStorageProvider(view);
+ _nativeControlHost = new NativeControlHostImpl(_view);
+ _storageProvider = new IOSStorageProvider(view);
}
public void Dispose()
@@ -157,9 +158,25 @@ namespace Avalonia.iOS
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
new AcrylicPlatformCompensationLevels();
- public ITextInputMethodImpl? TextInputMethod => _view;
- public INativeControlHostImpl NativeControlHost { get; }
- public IStorageProvider StorageProvider { get; }
+ public object? TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(IStorageProvider))
+ {
+ return _storageProvider;
+ }
+
+ if (featureType == typeof(ITextInputMethodImpl))
+ {
+ return _view;
+ }
+
+ if (featureType == typeof(INativeControlHostImpl))
+ {
+ return _nativeControlHost;
+ }
+
+ return null;
+ }
}
[Export("layerClass")]
diff --git a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
index 9b38422dde..41c0eb3958 100644
--- a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
@@ -9,6 +9,7 @@ using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Threading;
+using Avalonia.UnitTests;
using Xunit;
using Xunit.Sdk;
@@ -67,7 +68,7 @@ public class CompositionAnimationTests
public void GenericCheck(AnimationData data)
{
var compositor =
- new Compositor(new RenderLoop(new CompositorTestsBase.ManualRenderTimer(), new Dispatcher(null)), null);
+ new Compositor(new RenderLoop(new CompositorTestServices.ManualRenderTimer(), new Dispatcher(null)), null);
var target = compositor.CreateSolidColorVisual();
var ani = new ScalarKeyFrameAnimation(null);
foreach (var frame in data.Frames)
diff --git a/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs
index 466aba43ee..3d7dc66cc4 100644
--- a/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs
@@ -1,15 +1,7 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Controls;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Templates;
+using Avalonia.Controls;
using Avalonia.Input;
-using Avalonia.Input.Raw;
using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering;
using Avalonia.UnitTests;
-using Moq;
using Xunit;
namespace Avalonia.Base.UnitTests.Input
@@ -21,7 +13,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var device = new MouseDevice();
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -59,7 +51,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var device = new MouseDevice();
var impl = CreateTopLevelImplMock(renderer.Object);
diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
index 1ac50446c0..629188800a 100644
--- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var device = CreatePointerDeviceMock().Object;
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -50,7 +50,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var device = CreatePointerDeviceMock().Object;
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -93,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var device = CreatePointerDeviceMock(pointerType: PointerType.Touch).Object;
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -119,7 +119,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var pointer = new Mock();
var device = CreatePointerDeviceMock(pointer.Object).Object;
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -155,7 +155,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var device = CreatePointerDeviceMock().Object;
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -201,7 +201,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
var result = new List<(object?, string)>();
@@ -256,7 +256,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
var result = new List<(object?, string)>();
@@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests.Input
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
var expectedPosition = new Point(15, 15);
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
var result = new List<(object?, string, Point)>();
@@ -351,7 +351,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -405,7 +405,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
@@ -442,7 +442,7 @@ namespace Avalonia.Base.UnitTests.Input
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
index 510adb37b5..2d45c699f1 100644
--- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
@@ -35,6 +35,7 @@ public abstract class PointerTestsBase
impl.DefaultValue = DefaultValue.Mock;
impl.SetupAllProperties();
impl.SetupGet(r => r.RenderScaling).Returns(1);
+ impl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null);
impl.Setup(r => r.CreateRenderer(It.IsAny())).Returns(renderer);
impl.Setup(r => r.PointToScreen(It.IsAny())).Returns(p => new PixelPoint((int)p.X, (int)p.Y));
impl.Setup(r => r.PointToClient(It.IsAny())).Returns(p => new Point(p.X, p.Y));
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs
index 3d8369faeb..27bb0355e6 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs
@@ -20,7 +20,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Find_Controls_At_Point()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
var border = new Border
{
@@ -40,7 +40,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Find_Empty_Controls_At_Point()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
var border = new Border
{
@@ -59,7 +59,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Find_Invisible_Controls_At_Point()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Border visible, border;
s.TopLevel.Content = border = new Border
@@ -91,7 +91,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
public void HitTest_Should_Find_Zero_Opacity_Controls_At_Point(bool parent, bool child)
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Border visible, border;
s.TopLevel.Content = border = new Border
@@ -118,7 +118,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Find_Control_Outside_Point()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
var border = new Border
{
@@ -138,7 +138,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Return_Top_Controls_First()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Panel container = new Panel
{
@@ -173,7 +173,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Return_Top_Controls_First_With_ZIndex()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Panel container = new Panel
{
@@ -219,7 +219,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Border target;
Panel container = new Panel
@@ -259,7 +259,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Border target;
Panel container = new Panel
@@ -299,7 +299,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport()
{
- using (var s = new CompositorServices(new Size(100, 200)))
+ using (var s = new CompositorTestServices(new Size(100, 200)))
{
Border target;
Border item1;
@@ -373,7 +373,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Find_Path_When_Outside_Fill()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Path path = new Path
{
@@ -392,7 +392,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Respect_Geometry_Clip()
{
- using (var s = new CompositorServices(new Size(400, 400)))
+ using (var s = new CompositorTestServices(new Size(400, 400)))
{
Canvas canvas;
Border border = new Border
@@ -422,7 +422,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Accommodate_ICustomHitTest()
{
- using (var s = new CompositorServices(new Size(300, 200)))
+ using (var s = new CompositorTestServices(new Size(300, 200)))
{
Border border = new CustomHitTestBorder
{
@@ -445,7 +445,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Should_Not_Hit_Controls_Next_Pixel()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Border targetRectangle;
@@ -470,7 +470,7 @@ public class CompositorHitTestingTests : CompositorTestsBase
[Fact]
public void HitTest_Filter_Should_Filter_Out_Children()
{
- using (var s = new CompositorServices(new Size(200, 200)))
+ using (var s = new CompositorTestServices(new Size(200, 200)))
{
Border child, parent;
s.TopLevel.Content = parent = new Border
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
index 12db249f04..fed193e973 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
@@ -24,178 +24,7 @@ namespace Avalonia.Base.UnitTests.Rendering;
public class CompositorTestsBase
{
- public class DebugEvents : ICompositionTargetDebugEvents
- {
- public List Rects = new();
-
- public void RectInvalidated(Rect rc)
- {
- Rects.Add(rc);
- }
-
- public void Reset()
- {
- Rects.Clear();
- }
- }
-
- public class ManualRenderTimer : IRenderTimer
- {
- public event Action Tick;
- public bool RunsInBackground => false;
- public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero);
- public Task TriggerBackgroundTick() => Task.Run(TriggerTick);
- }
-
- class TopLevelImpl : ITopLevelImpl
- {
- private readonly Compositor _compositor;
- public CompositingRenderer Renderer { get; private set; }
-
- public TopLevelImpl(Compositor compositor, Size clientSize)
- {
- ClientSize = clientSize;
- _compositor = compositor;
- }
-
- public void Dispose()
- {
-
- }
-
- public Size ClientSize { get; }
- public Size? FrameSize { get; }
- public double RenderScaling => 1;
- public IEnumerable Surfaces { get; } = Array.Empty();
- public Action Input { get; set; }
- public Action Paint { get; set; }
- public Action Resized { get; set; }
- public Action ScalingChanged { get; set; }
- public Action TransparencyLevelChanged { get; set; }
-
- public IRenderer CreateRenderer(IRenderRoot root)
- {
- return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces);
- }
-
- public void Invalidate(Rect rect)
- {
- }
-
- public void SetInputRoot(IInputRoot inputRoot)
- {
- }
-
- public Point PointToClient(PixelPoint point) => default;
-
- public PixelPoint PointToScreen(Point point) => new();
-
- public void SetCursor(ICursorImpl cursor)
- {
- }
-
- public Action Closed { get; set; }
- public Action LostFocus { get; set; }
- public IMouseDevice MouseDevice { get; } = new MouseDevice();
- public IPopupImpl CreatePopup() => throw new NotImplementedException();
-
- public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
- {
- }
-
- public WindowTransparencyLevel TransparencyLevel { get; }
- public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
-
- public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
- }
-
- protected class CompositorServices : IDisposable
- {
- private readonly IDisposable _app;
- public Compositor Compositor { get; }
- public ManualRenderTimer Timer { get; } = new();
- public EmbeddableControlRoot TopLevel { get; }
- public CompositingRenderer Renderer { get; } = null!;
- public DebugEvents Events { get; } = new();
-
- public void Dispose()
- {
- TopLevel.Renderer.Stop();
- TopLevel.Dispose();
- _app.Dispose();
- }
-
- public CompositorServices(Size? size = null)
- {
- _app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
- try
- {
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(Timer);
- AvaloniaLocator.CurrentMutable.Bind()
- .ToConstant(new RenderLoop(Timer, Dispatcher.UIThread));
-
- Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null);
- var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000));
- TopLevel = new EmbeddableControlRoot(impl)
- {
- Template = new FuncControlTemplate((parent, scope) =>
- {
- var presenter = new ContentPresenter
- {
- [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
- };
- scope.Register("PART_ContentPresenter", presenter);
- return presenter;
- })
- };
- Renderer = impl.Renderer;
- TopLevel.Prepare();
- TopLevel.Renderer.Start();
- RunJobs();
- Renderer.CompositionTarget.Server.DebugEvents = Events;
- }
- catch
- {
- _app.Dispose();
- throw;
- }
- }
-
- public void RunJobs()
- {
- Dispatcher.UIThread.RunJobs();
- Timer.TriggerTick();
- Dispatcher.UIThread.RunJobs();
- }
-
- public void AssertRects(params Rect[] rects)
- {
- RunJobs();
- var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
- var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
- Assert.Equal(toAssert, invalidated);
- Events.Rects.Clear();
- }
-
- public void AssertHitTest(double x, double y, Func filter, params object[] expected)
- => AssertHitTest(new Point(x, y), filter, expected);
- public void AssertHitTest(Point pt, Func filter, params object[] expected)
- {
- RunJobs();
- var tested = Renderer.HitTest(pt, TopLevel, filter);
- Assert.Equal(expected, tested);
- }
-
- public void AssertHitTestFirst(Point pt, Func filter, object expected)
- {
- RunJobs();
- var tested = Renderer.HitTest(pt, TopLevel, filter).First();
- Assert.Equal(expected, tested);
- }
- }
-
-
- protected class CompositorCanvas : CompositorServices
+ protected class CompositorCanvas : CompositorTestServices
{
public Canvas Canvas { get; } = new();
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs b/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs
index fece152713..d65df992be 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs
@@ -8,8 +8,7 @@ namespace Avalonia.Base.UnitTests.Rendering
public bool HitTest(Point point)
{
// Move hit testing window halfway to the left
- return Bounds
- .WithX(Bounds.X - Bounds.Width / 2)
+ return new Rect( -Bounds.Width / 2,0, Bounds.Width, Bounds.Height)
.Contains(point);
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs
deleted file mode 100644
index f0c5a24cc4..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs
+++ /dev/null
@@ -1,790 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Controls;
-using Avalonia.Data;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Threading;
-using Avalonia.UnitTests;
-using Avalonia.Media.Imaging;
-using Avalonia.VisualTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering
-{
- public class DeferredRendererTests
- {
- [Fact]
- public void First_Frame_Calls_SceneBuilder_UpdateAll()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var root = new TestRoot();
- var sceneBuilder = MockSceneBuilder(root);
-
- CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object);
-
- sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()));
- }
- }
-
- [Fact]
- public void Frame_Does_Not_Call_SceneBuilder_If_No_Dirty_Controls()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
- var root = new TestRoot();
- var sceneBuilder = MockSceneBuilder(root);
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- renderTargetFactory: root.CreateRenderTarget,
- sceneBuilder: sceneBuilder.Object);
-
- target.Start();
- IgnoreFirstFrame(target, sceneBuilder);
- RunFrame(target);
-
- sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()), Times.Never);
- sceneBuilder.Verify(x => x.Update(It.IsAny(), It.IsAny()), Times.Never);
- }
- }
-
- [Fact]
- public void Should_Update_Dirty_Controls_In_Order()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- Border border;
- Decorator decorator;
- Canvas canvas;
-
- var root = new TestRoot
- {
- Child = decorator = new Decorator
- {
- Child = border = new Border { Child = canvas = new Canvas() }
- }
- };
-
- var sceneBuilder = MockSceneBuilder(root);
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder.Object,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- target.Start();
- IgnoreFirstFrame(target, sceneBuilder);
- target.AddDirty(border);
- target.AddDirty(canvas);
- target.AddDirty(root);
- target.AddDirty(decorator);
-
- var result = new List();
-
- sceneBuilder.Setup(x => x.Update(It.IsAny(), It.IsAny()))
- .Callback((_, v) => result.Add(v));
-
- RunFrame(target);
-
- Assert.Equal(new List { root, decorator, border, canvas }, result);
- }
- }
-
- [Fact]
- public void Should_Add_Dirty_Rect_On_Child_Remove()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- Decorator decorator;
- Border border;
-
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = decorator = new Decorator
- {
- Child = border = new Border { Width = 50, Height = 50, Background = Brushes.Red, },
- }
- };
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- target.Start();
- RunFrame(target);
-
- decorator.Child = null;
-
- RunFrame(target);
-
- var scene = target.UnitTestScene();
- var stackNode = scene.FindNode(decorator);
- var dirty = scene.Layers[0].Dirty.ToList();
-
- Assert.Equal(1, dirty.Count);
- Assert.Equal(new Rect(25, 25, 50, 50), dirty[0]);
- }
- }
-
- [Fact]
- public void Should_Update_VisualNode_Order_On_Child_Remove_Insert()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- StackPanel stack;
- Canvas canvas1;
- Canvas canvas2;
-
- var root = new TestRoot
- {
- Child = stack = new StackPanel
- {
- Children = { (canvas1 = new Canvas()), (canvas2 = new Canvas()), }
- }
- };
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- target.Start();
- RunFrame(target);
-
- stack.Children.Remove(canvas2);
- stack.Children.Insert(0, canvas2);
-
- RunFrame(target);
-
- var scene = target.UnitTestScene();
- var stackNode = scene.FindNode(stack);
-
- Assert.Same(stackNode.Children[0].Visual, canvas2);
- Assert.Same(stackNode.Children[1].Visual, canvas1);
- }
- }
-
- [Fact]
- public void Should_Update_VisualNode_Order_On_Child_Move()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- StackPanel stack;
- Canvas canvas1;
- Canvas canvas2;
-
- var root = new TestRoot
- {
- Child = stack = new StackPanel
- {
- Children = { (canvas1 = new Canvas()), (canvas2 = new Canvas()), }
- }
- };
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- target.Start();
- RunFrame(target);
-
- stack.Children.Move(1, 0);
-
- RunFrame(target);
-
- var scene = target.UnitTestScene();
- var stackNode = scene.FindNode(stack);
-
- Assert.Same(stackNode.Children[0].Visual, canvas2);
- Assert.Same(stackNode.Children[1].Visual, canvas1);
- }
- }
-
- [Fact]
- public void Should_Update_VisualNode_Order_On_ZIndex_Change()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- StackPanel stack;
- Canvas canvas1;
- Canvas canvas2;
-
- var root = new TestRoot
- {
- Child = stack = new StackPanel
- {
- Children =
- {
- (canvas1 = new Canvas { ZIndex = 1 }), (canvas2 = new Canvas { ZIndex = 2 }),
- }
- }
- };
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- target.Start();
- RunFrame(target);
-
- canvas1.ZIndex = 3;
-
- RunFrame(target);
-
- var scene = target.UnitTestScene();
- var stackNode = scene.FindNode(stack);
-
- Assert.Same(stackNode.Children[0].Visual, canvas2);
- Assert.Same(stackNode.Children[1].Visual, canvas1);
- }
- }
-
- [Fact]
- public void Should_Update_VisualNode_Order_On_ZIndex_Change_With_Dirty_Ancestor()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- StackPanel stack;
- Canvas canvas1;
- Canvas canvas2;
-
- var root = new TestRoot
- {
- Child = stack = new StackPanel
- {
- Children =
- {
- (canvas1 = new Canvas { ZIndex = 1 }), (canvas2 = new Canvas { ZIndex = 2 }),
- }
- }
- };
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- target.Start();
- RunFrame(target);
-
- root.InvalidateVisual();
- canvas1.ZIndex = 3;
-
- RunFrame(target);
-
- var scene = target.UnitTestScene();
- var stackNode = scene.FindNode(stack);
-
- Assert.Same(stackNode.Children[0].Visual, canvas2);
- Assert.Same(stackNode.Children[1].Visual, canvas1);
- }
- }
-
- [Fact]
- public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- Decorator moveFrom;
- Decorator moveTo;
- Canvas moveMe;
-
- var root = new TestRoot
- {
- Child = new StackPanel
- {
- Children =
- {
- (moveFrom = new Decorator { Child = moveMe = new Canvas(), }),
- (moveTo = new Decorator()),
- }
- }
- };
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- target.Start();
- RunFrame(target);
-
- moveFrom.Child = null;
- moveTo.Child = moveMe;
-
- RunFrame(target);
-
- var scene = target.UnitTestScene();
- var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
- var moveToNode = (VisualNode)scene.FindNode(moveTo);
-
- Assert.Empty(moveFromNode.Children);
- Assert.Equal(1, moveToNode.Children.Count);
- Assert.Same(moveMe, moveToNode.Children[0].Visual);
- }
- }
-
- [Fact]
- public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent_And_New_Root()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var dispatcher = new ImmediateDispatcher();
- var loop = new Mock();
-
- Decorator moveFrom;
- Decorator moveTo;
- Canvas moveMe;
-
- var root = new TestRoot
- {
- Child = new StackPanel
- {
- Children = { (moveFrom = new Decorator { Child = moveMe = new Canvas(), }) }
- }
- };
-
- var otherRoot = new TestRoot { Child = new StackPanel { Children = { (moveTo = new Decorator()) } } };
-
- var sceneBuilder = new SceneBuilder();
-
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- var otherSceneBuilder = new SceneBuilder();
-
- var otherTarget = new DeferredRenderer(
- otherRoot,
- loop.Object,
- sceneBuilder: otherSceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
-
- root.Renderer = target;
- otherRoot.Renderer = otherTarget;
-
- target.Start();
- otherTarget.Start();
-
- RunFrame(target);
- RunFrame(otherTarget);
-
- moveFrom.Child = null;
- moveTo.Child = moveMe;
-
- RunFrame(target);
- RunFrame(otherTarget);
-
- var scene = target.UnitTestScene();
- var otherScene = otherTarget.UnitTestScene();
-
- var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
- var moveToNode = (VisualNode)otherScene.FindNode(moveTo);
-
- Assert.Empty(moveFromNode.Children);
- Assert.Equal(1, moveToNode.Children.Count);
- Assert.Same(moveMe, moveToNode.Children[0].Visual);
- }
- }
-
- [Fact]
- public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var root = new TestRoot
- {
- Width = 100, Height = 100, Child = new Border { Background = Brushes.Red, Opacity = 0.5, }
- };
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var target = CreateTargetAndRunFrame(root);
- var context = GetLayerContext(target, root);
- var animation = new BehaviorSubject(0.5);
-
- context.Verify(x => x.PushOpacity(0.5), Times.Once);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once);
- context.Verify(x => x.PopOpacity(), Times.Once);
- }
- }
-
- [Fact]
- public void Should_Not_Draw_Controls_With_0_Opacity()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new Border
- {
- Background = Brushes.Red,
- Opacity = 0,
- Child = new Border { Background = Brushes.Green, }
- }
- };
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var target = CreateTargetAndRunFrame(root);
- var context = GetLayerContext(target, root);
- var animation = new BehaviorSubject(0.5);
-
- context.Verify(x => x.PushOpacity(0.5), Times.Never);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Never);
- context.Verify(x => x.PopOpacity(), Times.Never);
- }
- }
-
- [Fact]
- public void Should_Push_Opacity_Mask()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new Border { Background = Brushes.Red, OpacityMask = Brushes.Green, }
- };
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var target = CreateTargetAndRunFrame(root);
- var context = GetLayerContext(target, root);
- var animation = new BehaviorSubject(0.5);
-
- context.Verify(x => x.PushOpacityMask(Brushes.Green, new Rect(0, 0, 100, 100)), Times.Once);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once);
- context.Verify(x => x.PopOpacityMask(), Times.Once);
- }
- }
-
- [Fact]
- public void Should_Create_Layer_For_Root()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var root = new TestRoot();
- var rootLayer = new Mock();
-
- var sceneBuilder = new Mock();
-
- sceneBuilder.Setup(x => x.UpdateAll(It.IsAny()))
- .Callback(scene =>
- {
- scene.Size = root.ClientSize;
- scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize));
- });
-
- var renderInterface = new Mock();
- var target = CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object);
-
- Assert.Single(target.Layers);
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
-
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new Border
- {
- Background = Brushes.Red,
- Child = border = new Border
- {
- Background = Brushes.Green, Child = new Canvas(), Opacity = 0.9,
- }
- }
- };
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var timer = new Mock();
- var target = CreateTargetAndRunFrame(root, timer);
-
- Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
- RunFrame(target);
-
- Assert.Equal(new Visual[] { root, border }, target.Layers.Select(x => x.LayerRoot));
-
- animation.OnCompleted();
- RunFrame(target);
-
- Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
-
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new Border
- {
- Background = Brushes.Red, Child = border = new Border { Background = Brushes.Green, }
- }
- };
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var timer = new Mock();
- var target = CreateTargetAndRunFrame(root, timer);
-
- Assert.Single(target.Layers);
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
-
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = border = new Border { Background = Brushes.Red, Child = new Canvas(), }
- };
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var target = CreateTargetAndRunFrame(root);
- var context = GetLayerContext(target, border);
-
- context.Verify(x => x.PushOpacity(0.5), Times.Never);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once);
- context.Verify(x => x.PopOpacity(), Times.Never);
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Should_Draw_Transparent_Layer_With_Correct_Opacity()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
-
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = border = new Border { Background = Brushes.Red, Child = new Canvas(), }
- };
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var target = CreateTargetAndRunFrame(root);
- var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null));
- var borderLayer = target.Layers[border].Bitmap;
-
- context.Verify(x => x.DrawBitmap(borderLayer, 0.5, It.IsAny(), It.IsAny(),
- BitmapInterpolationMode.Default));
- }
- }
-
- [Fact]
- public void Can_Dirty_Control_In_SceneInvalidated()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border1;
- Border border2;
-
- var root = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new StackPanel
- {
- Children =
- {
- (border1 = new Border { Background = Brushes.Red, Child = new Canvas(), }),
- (border2 = new Border { Background = Brushes.Red, Child = new Canvas(), }),
- }
- }
- };
-
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var target = CreateTargetAndRunFrame(root);
- var invalidated = false;
-
- target.SceneInvalidated += (s, e) =>
- {
- invalidated = true;
- target.AddDirty(border2);
- };
-
- target.AddDirty(border1);
- target.Paint(new Rect(root.DesiredSize));
-
- Assert.True(invalidated);
- Assert.True(((IRenderLoopTask)target).NeedsUpdate);
- }
- }
-
- private DeferredRenderer CreateTargetAndRunFrame(
- TestRoot root,
- Mock timer = null,
- ISceneBuilder sceneBuilder = null,
- IDispatcher dispatcher = null)
- {
- timer = timer ?? new Mock();
- dispatcher = dispatcher ?? new ImmediateDispatcher();
- var target = new DeferredRenderer(
- root,
- new RenderLoop(timer.Object, dispatcher),
- sceneBuilder: sceneBuilder,
- renderTargetFactory: root.CreateRenderTarget,
- dispatcher: dispatcher);
- root.Renderer = target;
- target.Start();
- RunFrame(target);
- return target;
- }
-
- private Mock GetLayerContext(DeferredRenderer renderer, Control layerRoot)
- {
- return Mock.Get(renderer.Layers[layerRoot].Bitmap.Item.CreateDrawingContext(null));
- }
-
- private void IgnoreFirstFrame(IRenderLoopTask task, Mock sceneBuilder)
- {
- RunFrame(task);
- sceneBuilder.Invocations.Clear();
- }
-
- private void RunFrame(IRenderLoopTask task)
- {
- task.Update(TimeSpan.Zero);
- task.Render();
- }
-
- private IRenderTargetBitmapImpl CreateLayer()
- {
- return Mock.Of(x =>
- x.CreateDrawingContext(It.IsAny()) == Mock.Of());
- }
-
- private Mock MockSceneBuilder(IRenderRoot root)
- {
- var result = new Mock();
- result.Setup(x => x.UpdateAll(It.IsAny()))
- .Callback(x => x.Layers.Add((Visual)root).Dirty.Add(new Rect(root.ClientSize)));
- return result;
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
deleted file mode 100644
index 4f11af7327..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
+++ /dev/null
@@ -1,577 +0,0 @@
-using System;
-using System.Linq;
-using Avalonia.Controls;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Shapes;
-using Avalonia.Layout;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.UnitTests;
-using Avalonia.VisualTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering
-{
- public class DeferredRendererTests_HitTesting
- {
- [Fact]
- public void HitTest_Should_Find_Controls_At_Point()
- {
- using (TestApplication())
- {
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(new[] { root.Child }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Empty_Controls_At_Point()
- {
- using (TestApplication())
- {
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Empty(result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Invisible_Controls_At_Point()
- {
- using (TestApplication())
- {
- Border visible;
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- IsVisible = false,
- Child = visible = new Border
- {
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Stretch,
- VerticalAlignment = VerticalAlignment.Stretch,
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Empty(result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Control_Outside_Point()
- {
- using (TestApplication())
- {
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(10, 10), root, null);
-
- Assert.Empty(result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Return_Top_Controls_First()
- {
- using (TestApplication())
- {
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 200,
- Height = 200,
- Children =
- {
- new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- },
- new Border
- {
- Width = 50,
- Height = 50,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(new[] { container.Children[1], container.Children[0] }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Return_Top_Controls_First_With_ZIndex()
- {
- using (TestApplication())
- {
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 200,
- Height = 200,
- Children =
- {
- new Border
- {
- Width = 100,
- Height = 100,
- ZIndex = 1,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- },
- new Border
- {
- Width = 50,
- Height = 50,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- },
- new Border
- {
- Width = 75,
- Height = 75,
- ZIndex = 2,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds()
- {
- using (TestApplication())
- {
- Border target;
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 200,
- Height = 200,
- Background = Brushes.Red,
- ClipToBounds = false,
- Children =
- {
- new Border
- {
- Width = 100,
- Height = 100,
- ZIndex = 1,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- Child = target = new Border
- {
- Width = 50,
- Height = 50,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- RenderTransform = new TranslateTransform(110, 110),
- }
- },
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- container.Measure(Size.Infinity);
- container.Arrange(new Rect(container.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(120, 120), root, null);
-
- Assert.Equal(new Visual[] { target, container }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
- {
- using (TestApplication())
- {
- Border target;
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 100,
- Height = 200,
- Background = Brushes.Red,
- Children =
- {
- new Panel()
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Margin = new Thickness(0, 100, 0, 0),
- ClipToBounds = true,
- Children =
- {
- (target = new Border()
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Margin = new Thickness(0, -100, 0, 0)
- })
- }
- }
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(50, 50), root, null);
-
- Assert.Equal(new[] { container }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport()
- {
- using (TestApplication())
- {
- Border target;
- Border item1;
- Border item2;
- ScrollContentPresenter scroll;
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 100,
- Height = 200,
- Background = Brushes.Red,
- Children =
- {
- (target = new Border()
- {
- Name = "b1",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- }),
- new Border()
- {
- Name = "b2",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Margin = new Thickness(0, 100, 0, 0),
- Child = scroll = new ScrollContentPresenter()
- {
- CanHorizontallyScroll = true,
- CanVerticallyScroll = true,
- Content = new StackPanel()
- {
- Children =
- {
- (item1 = new Border()
- {
- Name = "b3",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- }),
- (item2 = new Border()
- {
- Name = "b4",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- }),
- }
- }
- }
- }
- }
- }
- };
-
- scroll.UpdateChild();
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
-
- root.Renderer.Paint(default);
- var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
-
- Assert.Equal(item1, result);
-
- result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
-
- Assert.Equal(target, result);
-
- scroll.Offset = new Vector(0, 100);
-
- // We don't have LayoutManager set up so do the layout pass manually.
- scroll.Parent.InvalidateArrange();
- container.InvalidateArrange();
- container.Arrange(new Rect(container.DesiredSize));
-
- root.Renderer.Paint(default);
- result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
- Assert.Equal(item2, result);
-
- result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
- Assert.Equal(target, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Path_When_Outside_Fill()
- {
- using (TestApplication())
- {
- Path path;
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = path = new Path
- {
- Width = 200,
- Height = 200,
- Fill = Brushes.Red,
- Data = StreamGeometry.Parse("M100,0 L0,100 100,100")
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var context = new DrawingContext(Mock.Of());
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
- Assert.Equal(new[] { path }, result);
-
- result = root.Renderer.HitTest(new Point(10, 10), root, null);
- Assert.Empty(result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Respect_Geometry_Clip()
- {
- using (TestApplication())
- {
- Border border;
- Canvas canvas;
- var root = new TestRoot
- {
- Width = 400,
- Height = 400,
- Child = border = new Border
- {
- Background = Brushes.Red,
- Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"),
- Width = 200,
- Height = 200,
- Child = canvas = new Canvas
- {
- Background = Brushes.Yellow,
- Margin = new Thickness(10),
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
- Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds);
-
- var context = new DrawingContext(Mock.Of());
-
- var result = root.Renderer.HitTest(new Point(200, 200), root, null);
- Assert.Equal(new Visual[] { canvas, border }, result);
-
- result = root.Renderer.HitTest(new Point(110, 110), root, null);
- Assert.Empty(result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Accommodate_ICustomHitTest()
- {
- using (TestApplication())
- {
- Border border;
-
- var root = new TestRoot
- {
- Width = 300,
- Height = 200,
- Child = border = new CustomHitTestBorder
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(75, 100), root, null);
- Assert.Equal(new[] { border }, result);
-
- result = root.Renderer.HitTest(new Point(125, 100), root, null);
- Assert.Equal(new[] { border }, result);
-
- result = root.Renderer.HitTest(new Point(175, 100), root, null);
- Assert.Empty(result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Hit_Controls_Next_Pixel()
- {
- using (TestApplication())
- {
- Border targetRectangle;
-
- var root = new TestRoot
- {
- Width = 50,
- Height = 200,
- Child = new StackPanel
- {
- Orientation = Orientation.Vertical,
- HorizontalAlignment = HorizontalAlignment.Left,
- Children =
- {
- new Border { Width = 50, Height = 50, Background = Brushes.Red},
- { targetRectangle = new Border { Width = 50, Height = 50, Background = Brushes.Green} }
- }
- }
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = root.Renderer.HitTest(new Point(25, 50), root, null);
- Assert.Equal(new[] { targetRectangle }, result);
- }
- }
-
- private static IDisposable TestApplication()
- {
- return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs
deleted file mode 100644
index a07191f464..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs
+++ /dev/null
@@ -1,273 +0,0 @@
-using System.Collections.Generic;
-using Avalonia.Controls;
-using Avalonia.Layout;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.UnitTests;
-using Avalonia.VisualTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering
-{
- public class ImmediateRendererTests
- {
- [Fact]
- public void AddDirty_Call_RenderRoot_Invalidate()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var child = new Border
- {
- Width = 100,
- Height = 100,
- Margin = new(10),
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- };
-
- var root = new RenderRoot
- {
- Child = child,
- Width = 400,
- Height = 400,
- };
-
- root.LayoutManager.ExecuteInitialLayoutPass();
-
- var target = new ImmediateRenderer(root, root.CreateRenderTarget);
-
- target.AddDirty(child);
-
- Assert.Equal(new[] { new Rect(10, 10, 100, 100) }, root.Invalidations);
- }
- }
-
- [Fact]
- public void AddDirty_With_RenderTransform_Call_RenderRoot_Invalidate()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var child = new Border
- {
- Width = 100,
- Height = 100,
- Margin = new(100),
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- };
-
- var root = new RenderRoot
- {
- Child = child,
- Width = 400,
- Height = 400,
- };
-
- root.LayoutManager.ExecuteInitialLayoutPass();
-
- child.RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 };
-
- var target = new ImmediateRenderer(root, root.CreateRenderTarget);
-
- target.AddDirty(child);
-
- Assert.Equal(new[] { new Rect(50, 50, 200, 200) }, root.Invalidations);
- }
- }
-
- [Fact]
- public void AddDirty_For_Child_Moved_Should_Invalidate_Previous_Bounds()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var child = new Border
- {
- Width = 100,
- Height = 100,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- };
-
- var root = new RenderRoot
- {
- Child = child,
- Width = 400,
- Height = 400,
- };
-
- var target = new ImmediateRenderer(root, root.CreateRenderTarget);
-
- root.LayoutManager.ExecuteInitialLayoutPass();
- target.AddDirty(child);
-
- Assert.Equal(new Rect(0, 0, 100, 100), root.Invalidations[0]);
-
- target.Paint(new Rect(0, 0, 100, 100));
-
- //move child 100 pixels bottom/right
- child.Margin = new(100, 100);
- root.LayoutManager.ExecuteLayoutPass();
-
- //renderer should invalidate old child bounds with new one
- //as on old area there can be artifacts
- target.AddDirty(child);
-
- //invalidate first old position
- Assert.Equal(new Rect(0, 0, 100, 100), root.Invalidations[1]);
-
- //then new position
- Assert.Equal(new Rect(100, 100, 100, 100), root.Invalidations[2]);
- }
- }
-
- [Fact]
- public void Should_Not_Clip_Children_With_RenderTransform_When_In_Bounds()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- const int RootWidth = 300;
- const int RootHeight = 300;
-
- var rootGrid = new Grid { Width = RootWidth, Height = RootHeight, ClipToBounds = true };
-
- var stackPanel = new StackPanel
- {
- Orientation = Orientation.Horizontal,
- VerticalAlignment = VerticalAlignment.Top,
- HorizontalAlignment = HorizontalAlignment.Right,
- Margin = new Thickness(0, 10, 0, 0),
- RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Relative),
- RenderTransform = new TransformGroup
- {
- Children = { new RotateTransform { Angle = 90 }, new TranslateTransform { X = 240 } }
- }
- };
-
- rootGrid.Children.Add(stackPanel);
-
- TestControl CreateControl()
- => new TestControl
- {
- Width = 80,
- Height = 40,
- Margin = new Thickness(0, 0, 5, 0),
- ClipToBounds = true
- };
-
- var control1 = CreateControl();
- var control2 = CreateControl();
- var control3 = CreateControl();
-
- stackPanel.Children.Add(control1);
- stackPanel.Children.Add(control2);
- stackPanel.Children.Add(control3);
-
- var root = new TestRoot(rootGrid);
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.LayoutManager.ExecuteInitialLayoutPass();
-
- var rootSize = new Size(RootWidth, RootHeight);
- root.Measure(rootSize);
- root.Arrange(new Rect(rootSize));
-
- root.Renderer.Paint(root.Bounds);
-
- Assert.True(control1.Rendered);
- Assert.True(control2.Rendered);
- Assert.True(control3.Rendered);
- }
- }
-
- [Fact]
- public void Should_Not_Render_Clipped_Child_With_RenderTransform_When_Not_In_Bounds()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- const int RootWidth = 300;
- const int RootHeight = 300;
-
- var rootGrid = new Grid { Width = RootWidth, Height = RootHeight, ClipToBounds = true };
-
- var stackPanel = new StackPanel
- {
- Orientation = Orientation.Horizontal,
- VerticalAlignment = VerticalAlignment.Top,
- HorizontalAlignment = HorizontalAlignment.Right,
- Margin = new Thickness(0, 10, 0, 0),
- RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Relative),
- RenderTransform = new TransformGroup
- {
- Children = { new RotateTransform { Angle = 90 }, new TranslateTransform { X = 280 } }
- }
- };
-
- rootGrid.Children.Add(stackPanel);
-
- TestControl CreateControl()
- => new TestControl
- {
- Width = 160,
- Height = 40,
- Margin = new Thickness(0, 0, 5, 0),
- ClipToBounds = true
- };
-
- var control1 = CreateControl();
- var control2 = CreateControl();
- var control3 = CreateControl();
-
- stackPanel.Children.Add(control1);
- stackPanel.Children.Add(control2);
- stackPanel.Children.Add(control3);
-
- var root = new TestRoot(rootGrid);
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.LayoutManager.ExecuteInitialLayoutPass();
-
- var rootSize = new Size(RootWidth, RootHeight);
- root.Measure(rootSize);
- root.Arrange(new Rect(rootSize));
-
- root.Renderer.Paint(root.Bounds);
-
- Assert.True(control1.Rendered);
- Assert.True(control2.Rendered);
- Assert.False(control3.Rendered);
- }
- }
-
- [Fact]
- public void Static_Render_Method_Does_Not_Update_TransformedBounds()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var target = new Border();
- var expected = new TransformedBounds(new Rect(1, 2, 3, 4), new Rect(4, 5, 6, 7), Matrix.CreateRotation(0.8));
-
- target.SetTransformedBounds(expected);
-
- var renderTarget = Mock.Of(x =>
- x.CreateDrawingContext(It.IsAny()) == Mock.Of());
- ImmediateRenderer.Render(target, renderTarget);
-
- Assert.Equal(expected, target.TransformedBounds);
- }
- }
-
- private class RenderRoot : TestRoot, IRenderRoot
- {
- public List Invalidations { get; } = new();
- void IRenderRoot.Invalidate(Rect rect) => Invalidations.Add(rect);
- }
-
- private class TestControl : Control
- {
- public bool Rendered { get; private set; }
-
- public override void Render(DrawingContext context)
- => Rendered = true;
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
deleted file mode 100644
index 9ce8c42e33..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
+++ /dev/null
@@ -1,458 +0,0 @@
-using System;
-using System.Linq;
-using Avalonia.Controls;
-using Avalonia.Controls.Presenters;
-using Avalonia.Layout;
-using Avalonia.Media;
-using Avalonia.Rendering;
-using Avalonia.UnitTests;
-using Avalonia.VisualTree;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering
-{
- public class ImmediateRendererTests_HitTesting
- {
- [Fact]
- public void HitTest_Should_Find_Controls_At_Point()
- {
- using (TestApplication())
- {
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(new[] { root.Child, root }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Invisible_Controls_At_Point()
- {
- using (TestApplication())
- {
- Border visible;
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- IsVisible = false,
- Child = visible = new Border
- {
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Stretch,
- VerticalAlignment = VerticalAlignment.Stretch,
- }
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(new[] { root }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Control_Outside_Point()
- {
- using (TestApplication())
- {
- var root = new TestRoot
- {
- Width = 200,
- Height = 200,
- Child = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(10, 10), root, null);
-
- Assert.Equal(new[] { root }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Return_Top_Controls_First()
- {
- using (TestApplication())
- {
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 200,
- Height = 200,
- Children =
- {
- new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- },
- new Border
- {
- Width = 50,
- Height = 50,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- }
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(new[] { container.Children[1], container.Children[0], container, root }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Return_Top_Controls_First_With_ZIndex()
- {
- using (TestApplication())
- {
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 200,
- Height = 200,
- Children =
- {
- new Border
- {
- Width = 100,
- Height = 100,
- ZIndex = 1,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- },
- new Border
- {
- Width = 50,
- Height = 50,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- },
- new Border
- {
- Width = 75,
- Height = 75,
- ZIndex = 2,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- }
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(100, 100), root, null);
-
- Assert.Equal(
- new[]
- {
- container.Children[2],
- container.Children[0],
- container.Children[1],
- container,
- root
- },
- result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds()
- {
- using (TestApplication())
- {
- Border target;
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 200,
- Height = 200,
- Background = Brushes.Red,
- ClipToBounds = false,
- Children =
- {
- new Border
- {
- Width = 100,
- Height = 100,
- ZIndex = 1,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- Child = target = new Border
- {
- Width = 50,
- Height = 50,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- RenderTransform = new TranslateTransform(110, 110),
- }
- },
- }
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- container.Measure(Size.Infinity);
- container.Arrange(new Rect(container.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(120, 120), root, null);
-
- Assert.Equal(new Visual[] { target, container }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
- {
- using (TestApplication())
- {
- Border target;
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 100,
- Height = 200,
- Background = Brushes.Red,
- Children =
- {
- new Panel()
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Margin = new Thickness(0, 100, 0, 0),
- ClipToBounds = true,
- Children =
- {
- (target = new Border()
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Margin = new Thickness(0, -100, 0, 0)
- })
- }
- }
- }
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(50, 50), root, null);
-
- Assert.Equal(new Visual[] { container, root }, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport()
- {
- using (TestApplication())
- {
- Border target;
- Border item1;
- Border item2;
- ScrollContentPresenter scroll;
- Panel container;
- var root = new TestRoot
- {
- Child = container = new Panel
- {
- Width = 100,
- Height = 200,
- Background = Brushes.Red,
- Children =
- {
- (target = new Border()
- {
- Name = "b1",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- }),
- new Border()
- {
- Name = "b2",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Margin = new Thickness(0, 100, 0, 0),
- Child = scroll = new ScrollContentPresenter()
- {
- CanHorizontallyScroll = true,
- CanVerticallyScroll = true,
- Content = new StackPanel()
- {
- Children =
- {
- (item1 = new Border()
- {
- Name = "b3",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- }),
- (item2 = new Border()
- {
- Name = "b4",
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- }),
- }
- }
- }
- }
- }
- }
- };
-
- scroll.UpdateChild();
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(container.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
-
- Assert.Equal(item1, result);
-
- result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
-
- Assert.Equal(target, result);
-
- scroll.Offset = new Vector(0, 100);
-
- // We don't have LayoutManager set up so do the layout pass manually.
- scroll.Parent.InvalidateArrange();
- container.InvalidateArrange();
- container.Arrange(new Rect(container.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
- Assert.Equal(item2, result);
-
- result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
- Assert.Equal(target, result);
- }
- }
-
- [Fact]
- public void HitTest_Should_Accommodate_ICustomHitTest()
- {
- using (TestApplication())
- {
- Border border;
-
- var root = new TestRoot
- {
- Width = 300,
- Height = 200,
- Child = border = new CustomHitTestBorder
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- };
-
- root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
- root.Renderer.Paint(new Rect(root.ClientSize));
-
- var result = root.Renderer.HitTest(new Point(75, 100), root, null).First();
- Assert.Equal(border, result);
-
- result = root.Renderer.HitTest(new Point(125, 100), root, null).First();
- Assert.Equal(border, result);
-
- result = root.Renderer.HitTest(new Point(175, 100), root, null).First();
- Assert.Equal(root, result);
- }
- }
-
- private static IDisposable TestApplication()
- {
- return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
deleted file mode 100644
index 4cdb11b468..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-using System.Linq;
-using Avalonia.Media;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.UnitTests;
-using Avalonia.Utilities;
-using Avalonia.VisualTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
-{
- public class DeferredDrawingContextImplTests
- {
- [Fact]
- public void Should_Add_VisualNode()
- {
- var parent = new VisualNode(new TestRoot(), null);
- var child = new VisualNode(Mock.Of(), parent);
- var layers = new SceneLayers(parent.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- target.BeginUpdate(parent);
- target.BeginUpdate(child);
-
- Assert.Equal(1, parent.Children.Count);
- Assert.Same(child, parent.Children[0]);
- }
-
- [Fact]
- public void Should_Not_Replace_Identical_VisualNode()
- {
- var parent = new VisualNode(new TestRoot(), null);
- var child = new VisualNode(Mock.Of(), parent);
- var layers = new SceneLayers(parent.Visual);
-
- parent.AddChild(child);
-
- var target = new DeferredDrawingContextImpl(null, layers);
-
- target.BeginUpdate(parent);
- target.BeginUpdate(child);
-
- Assert.Equal(1, parent.Children.Count);
- Assert.Same(child, parent.Children[0]);
- }
-
- [Fact]
- public void Should_Replace_Different_VisualNode()
- {
- var parent = new VisualNode(new TestRoot(), null);
- var child1 = new VisualNode(Mock.Of(), parent);
- var child2 = new VisualNode(Mock.Of(), parent);
- var layers = new SceneLayers(parent.Visual);
-
- parent.AddChild(child1);
-
- var target = new DeferredDrawingContextImpl(null, layers);
-
- target.BeginUpdate(parent);
- target.BeginUpdate(child2);
-
- Assert.Equal(1, parent.Children.Count);
- Assert.Same(child2, parent.Children[0]);
- }
-
- [Fact]
- public void TrimChildren_Should_Trim_Children()
- {
- var root = new TestRoot();
- var node = new VisualNode(root, null) { LayerRoot = root };
-
- node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root });
- node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root });
- node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root });
- node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root });
-
- var layers = new SceneLayers(root);
- var target = new DeferredDrawingContextImpl(null, layers);
- var child1 = new VisualNode(Mock.Of(), node) { LayerRoot = root };
- var child2 = new VisualNode(Mock.Of(), node) { LayerRoot = root };
-
- target.BeginUpdate(node);
- using (target.BeginUpdate(child1)) { }
- using (target.BeginUpdate(child2)) { }
- target.TrimChildren();
-
- Assert.Equal(2, node.Children.Count);
- }
-
- [Fact]
- public void Should_Add_DrawOperations()
- {
- var node = new VisualNode(new TestRoot(), null);
- var layers = new SceneLayers(node.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- node.LayerRoot = node.Visual;
-
- using (target.BeginUpdate(node))
- {
- target.DrawRectangle(Brushes.Red, new Pen(Brushes.Green, 1), new Rect(0, 0, 100, 100));
- }
-
- Assert.Equal(1, node.DrawOperations.Count);
- Assert.IsType(node.DrawOperations[0].Item);
- }
-
- [Fact]
- public void Should_Not_Replace_Identical_DrawOperation()
- {
- var node = new VisualNode(new TestRoot(), null);
- var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default));
- var layers = new SceneLayers(node.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- node.LayerRoot = node.Visual;
- node.AddDrawOperation(operation);
-
- using (target.BeginUpdate(node))
- {
- target.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100));
- }
-
- Assert.Equal(1, node.DrawOperations.Count);
- Assert.Same(operation.Item, node.DrawOperations.Single().Item);
-
- Assert.IsType(node.DrawOperations[0].Item);
- }
-
- [Fact]
- public void Should_Replace_Different_DrawOperation()
- {
- var node = new VisualNode(new TestRoot(), null);
- var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default));
- var layers = new SceneLayers(node.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- node.LayerRoot = node.Visual;
- node.AddDrawOperation(operation);
-
- using (target.BeginUpdate(node))
- {
- target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 100, 100));
- }
-
- Assert.Equal(1, node.DrawOperations.Count);
- Assert.NotSame(operation, node.DrawOperations.Single());
-
- Assert.IsType(node.DrawOperations[0].Item);
- }
-
- [Fact]
- public void Should_Update_DirtyRects()
- {
- var node = new VisualNode(new TestRoot(), null);
- var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default);
- var layers = new SceneLayers(node.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- node.LayerRoot = node.Visual;
-
- using (target.BeginUpdate(node))
- {
- target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 100, 100));
- }
-
- Assert.Equal(new Rect(0, 0, 100, 100), layers.Single().Dirty.Single());
- }
-
- [Fact]
- public void Should_Trim_DrawOperations()
- {
- var node = new VisualNode(new TestRoot(), null);
- node.LayerRoot = node.Visual;
-
- for (var i = 0; i < 4; ++i)
- {
- var drawOperation = new Mock();
- using (var r = RefCountable.Create(drawOperation.Object))
- {
- node.AddDrawOperation(r);
- }
- }
-
- var drawOperations = node.DrawOperations.Select(op => op.Item).ToList();
- var layers = new SceneLayers(node.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- using (target.BeginUpdate(node))
- {
- target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 10, 100));
- target.DrawRectangle(Brushes.Blue, null, new Rect(0, 0, 20, 100));
- }
-
- Assert.Equal(2, node.DrawOperations.Count);
-
- foreach (var i in drawOperations)
- {
- Mock.Get(i).Verify(x => x.Dispose());
- }
- }
-
- [Fact]
- public void Trimmed_DrawOperations_Releases_Reference()
- {
- var node = new VisualNode(new TestRoot(), null);
- var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default));
- var layers = new SceneLayers(node.Visual);
- var target = new DeferredDrawingContextImpl(null, layers);
-
- node.LayerRoot = node.Visual;
- node.AddDrawOperation(operation);
- Assert.Equal(2, operation.RefCount);
-
- using (target.BeginUpdate(node))
- {
- target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 100, 100));
- }
-
- Assert.Equal(1, node.DrawOperations.Count);
- Assert.NotSame(operation, node.DrawOperations.Single());
- Assert.Equal(1, operation.RefCount);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
deleted file mode 100644
index a3d11a76aa..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
+++ /dev/null
@@ -1,1087 +0,0 @@
-using System.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Controls;
-using Avalonia.Data;
-using Avalonia.Layout;
-using Avalonia.Media;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.UnitTests;
-using Avalonia.Utilities;
-using Avalonia.VisualTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
-{
- public partial class SceneBuilderTests
- {
- [Fact]
- public void Should_Build_Initial_Scene()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- TextBlock textBlock;
- var tree = new TestRoot
- {
- Child = border = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Child = textBlock = new TextBlock
- {
- TextWrapping = TextWrapping.NoWrap,
- Text = "Hello World",
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var result = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(result);
-
- Assert.Same(tree, ((VisualNode)result.Root).LayerRoot);
- Assert.Equal(1, result.Root.Children.Count);
-
- var borderNode = (VisualNode)result.Root.Children[0];
- Assert.Same(borderNode, result.FindNode(border));
- Assert.Same(border, borderNode.Visual);
- Assert.Equal(1, borderNode.Children.Count);
- Assert.Equal(1, borderNode.DrawOperations.Count);
-
- var backgroundNode = (RectangleNode)borderNode.DrawOperations[0].Item;
- Assert.Equal(Brushes.Red, backgroundNode.Brush);
-
- var textBlockNode = borderNode.Children[0];
- Assert.Same(textBlockNode, result.FindNode(textBlock));
- Assert.Same(textBlock, textBlockNode.Visual);
- Assert.Equal(1, textBlockNode.DrawOperations.Count);
-
- var textNode = (GlyphRunNode)textBlockNode.DrawOperations[0].Item;
- Assert.NotNull(textNode.GlyphRun);
- }
- }
-
- [Fact]
- public void Should_Respect_Margin_For_ClipBounds()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Canvas canvas;
- var tree = new TestRoot
- {
- Width = 200,
- Height = 300,
- Child = new Border
- {
- Margin = new Thickness(10, 20, 30, 40),
- Child = canvas = new Canvas
- {
- ClipToBounds = true,
- Background = Brushes.AliceBlue,
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var result = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(result);
-
- var canvasNode = result.FindNode(canvas);
- Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds);
-
- // Initial ClipBounds are correct, make sure they're still correct after updating canvas.
- result = result.CloneScene();
- Assert.True(sceneBuilder.Update(result, canvas));
-
- canvasNode = result.FindNode(canvas);
- Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds);
- }
- }
-
- [Fact]
- public void ClipBounds_Should_Be_Intersection_With_Parent_ClipBounds()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- var tree = new TestRoot
- {
- Width = 200,
- Height = 300,
- Child = new Canvas
- {
- ClipToBounds = true,
- Width = 100,
- Height = 100,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- Children =
- {
- (border = new Border
- {
- Background = Brushes.AliceBlue,
- ClipToBounds = true,
- Width = 100,
- Height = 100,
- [Canvas.LeftProperty] = 50,
- [Canvas.TopProperty] = 50,
- })
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var borderNode = scene.FindNode(border);
- Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds);
- }
- }
-
- [Fact]
- public void Should_Update_Descendent_ClipBounds_When_Margin_Changed()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Width = 200,
- Height = 300,
- Child = canvas = new Canvas
- {
- ClipToBounds = true,
- Width = 100,
- Height = 100,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- Children =
- {
- (border = new Border
- {
- Background = Brushes.AliceBlue,
- ClipToBounds = true,
- Width = 100,
- Height = 100,
- [Canvas.LeftProperty] = 50,
- [Canvas.TopProperty] = 50,
- })
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var borderNode = scene.FindNode(border);
- Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds);
-
- canvas.Width = canvas.Height = 125;
- canvas.Measure(Size.Infinity);
- canvas.Arrange(new Rect(tree.DesiredSize));
-
- // Initial ClipBounds are correct, make sure they're still correct after updating canvas.
- scene = scene.CloneScene();
- Assert.True(sceneBuilder.Update(scene, canvas));
-
- borderNode = scene.FindNode(border);
- Assert.Equal(new Rect(50, 50, 75, 75), borderNode.ClipBounds);
- }
- }
-
- [Fact]
- public void Should_Respect_ZIndex()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border front;
- Border back;
- var tree = new TestRoot
- {
- Child = new Panel
- {
- Children =
- {
- (front = new Border
- {
- ZIndex = 1,
- }),
- (back = new Border
- {
- ZIndex = 0,
- }),
- }
- }
- };
-
- var result = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(result);
-
- var panelNode = result.FindNode(tree.Child);
- var expected = new Visual[] { back, front };
- var actual = panelNode.Children.OfType().Select(x => x.Visual).ToArray();
- Assert.Equal(expected, actual);
- }
- }
-
- [Fact]
- public void Should_Respect_Uniform_ZIndex()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Panel panel;
-
- var tree = new TestRoot
- {
- Child = panel = new Panel()
- };
-
- for (var i = 0; i < 128; i++)
- {
- panel.Children.Add(new Border());
- }
-
- var result = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(result);
-
- var panelNode = result.FindNode(tree.Child);
- var expected = panel.Children.ToArray();
- var actual = panelNode.Children.OfType().Select(x => x.Visual).ToArray();
- Assert.Equal(expected, actual);
- }
- }
-
- [Fact]
- public void ClipBounds_Should_Be_In_Global_Coordinates()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border target;
- var tree = new TestRoot
- {
- Child = new Decorator
- {
- Margin = new Thickness(24, 26),
- Child = target = new Border
- {
- ClipToBounds = true,
- Margin = new Thickness(26, 24),
- Width = 100,
- Height = 100,
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var result = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(result);
-
- var targetNode = result.FindNode(target);
-
- Assert.Equal(new Rect(50, 50, 100, 100), targetNode.ClipBounds);
- }
- }
-
- [Fact]
- public void Transform_For_Control_With_RenderTransform_Should_Be_Correct_After_Update()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- var tree = new TestRoot
- {
- Width = 400,
- Height = 200,
- Child = new Decorator
- {
- Width = 200,
- Height = 100,
- Child = border = new Border
- {
- Background = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Right,
- Width = 100,
- RenderTransform = new ScaleTransform(0.5, 1),
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var expectedTransform = Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(225, 50);
- var borderNode = scene.FindNode(border);
- Assert.Equal(expectedTransform, borderNode.Transform);
-
- scene = scene.CloneScene();
- Assert.True(sceneBuilder.Update(scene, border));
-
- borderNode = scene.FindNode(border);
- Assert.Equal(expectedTransform, borderNode.Transform);
- }
- }
-
- [Fact]
- public void MirrorTransform_For_Control_With_RenderTransform_Should_Be_Correct()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- var tree = new TestRoot
- {
- Width = 400,
- Height = 200,
- Child = border = new Border
- {
- HorizontalAlignment = HorizontalAlignment.Left,
- Background = Brushes.Red,
- Width = 100,
- RenderTransform = new ScaleTransform(0.5, 1),
- FlowDirection = FlowDirection.RightToLeft
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var expectedTransform = new Matrix(-1, 0, 0, 1, 100, 0) * Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(25, 0);
- var borderNode = scene.FindNode(border);
- Assert.Equal(expectedTransform, borderNode.Transform);
- }
- }
-
- [Fact]
- public void Should_Update_Border_Background_Node()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- TextBlock textBlock;
- var tree = new TestRoot
- {
- Child = border = new Border
- {
- Width = 100,
- Height = 100,
- Background = Brushes.Red,
- Child = textBlock = new TextBlock
- {
- Foreground = Brushes.Green,
- Text = "Hello World",
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var initial = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(initial);
-
- var initialBackgroundNode = initial.FindNode(border).Children[0];
- var initialTextNode = initial.FindNode(textBlock).DrawOperations[0];
-
- Assert.NotNull(initialBackgroundNode);
- Assert.NotNull(initialTextNode);
-
- border.Background = Brushes.Green;
-
- var result = initial.CloneScene();
- sceneBuilder.Update(result, border);
-
- var borderNode = (VisualNode)result.Root.Children[0];
- Assert.Same(border, borderNode.Visual);
-
- var backgroundNode = (RectangleNode)borderNode.DrawOperations[0].Item;
- Assert.NotSame(initialBackgroundNode, backgroundNode);
- Assert.Equal(Brushes.Green, backgroundNode.Brush);
-
- var textBlockNode = (VisualNode)borderNode.Children[0];
- Assert.Same(textBlock, textBlockNode.Visual);
-
- var textNode = (GlyphRunNode)textBlockNode.DrawOperations[0].Item;
- Assert.Same(initialTextNode.Item, textNode);
- }
- }
-
- [Fact]
- public void Should_Update_When_Control_Added()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = border = new Border
- {
- Background = Brushes.Red,
- }
- };
-
- Canvas canvas;
- var decorator = new Decorator
- {
- Child = canvas = new Canvas(),
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var initial = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(initial);
-
- border.Child = decorator;
- var result = initial.CloneScene();
-
- Assert.True(sceneBuilder.Update(result, decorator));
-
- // Updating canvas should result in no-op as it should have been updated along
- // with decorator as part of the add opeation.
- Assert.False(sceneBuilder.Update(result, canvas));
-
- var borderNode = (VisualNode)result.Root.Children[0];
- Assert.Equal(1, borderNode.Children.Count);
- Assert.Equal(1, borderNode.DrawOperations.Count);
-
- var decoratorNode = (VisualNode)borderNode.Children[0];
- Assert.Same(decorator, decoratorNode.Visual);
- Assert.Same(decoratorNode, result.FindNode(decorator));
-
- var canvasNode = (VisualNode)decoratorNode.Children[0];
- Assert.Same(canvas, canvasNode.Visual);
- Assert.Same(canvasNode, result.FindNode(canvas));
- }
- }
-
- [Fact]
- public void Should_Update_When_Control_Removed()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Border border;
- Decorator decorator;
- Canvas canvas;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = border = new Border
- {
- Background = Brushes.Red,
- Child = decorator = new Decorator
- {
- Child = canvas = new Canvas
- {
- Background = Brushes.AliceBlue,
- }
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var initial = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(initial);
-
- border.Child = null;
- var result = initial.CloneScene();
-
- Assert.True(sceneBuilder.Update(result, decorator));
- Assert.False(sceneBuilder.Update(result, canvas));
-
- var borderNode = (VisualNode)result.Root.Children[0];
- Assert.Equal(0, borderNode.Children.Count);
- Assert.Equal(1, borderNode.DrawOperations.Count);
-
- Assert.Null(result.FindNode(decorator));
- Assert.Equal(new Rect(0, 0, 100, 100), result.Layers.Single().Dirty.Single());
- }
- }
-
- [Fact]
- public void Should_Update_When_Control_Moved()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator moveFrom;
- Decorator moveTo;
- Canvas moveMe;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new StackPanel
- {
- Children =
- {
- (moveFrom = new Decorator
- {
- Child = moveMe = new Canvas(),
- }),
- (moveTo = new Decorator()),
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
- var moveToNode = (VisualNode)scene.FindNode(moveTo);
-
- Assert.Equal(1, moveFromNode.Children.Count);
- Assert.Same(moveMe, moveFromNode.Children[0].Visual);
- Assert.Empty(moveToNode.Children);
-
- moveFrom.Child = null;
- moveTo.Child = moveMe;
-
- scene = scene.CloneScene();
- moveFromNode = (VisualNode)scene.FindNode(moveFrom);
- moveToNode = (VisualNode)scene.FindNode(moveTo);
-
- moveFromNode.SortChildren(scene);
- moveToNode.SortChildren(scene);
- sceneBuilder.Update(scene, moveFrom);
- sceneBuilder.Update(scene, moveTo);
- sceneBuilder.Update(scene, moveMe);
-
- Assert.Empty(moveFromNode.Children);
- Assert.Equal(1, moveToNode.Children.Count);
- Assert.Same(moveMe, moveToNode.Children[0].Visual);
- }
- }
-
- [Fact]
- public void Should_Update_When_Control_Moved_Causing_Layout_Change()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator moveFrom;
- Decorator moveTo;
- Canvas moveMe;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = new DockPanel
- {
- Children =
- {
- (moveFrom = new Decorator
- {
- Child = moveMe = new Canvas
- {
- Width = 100,
- Height = 100,
- },
- }),
- (moveTo = new Decorator()),
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
- var moveToNode = (VisualNode)scene.FindNode(moveTo);
-
- Assert.Equal(1, moveFromNode.Children.Count);
- Assert.Same(moveMe, moveFromNode.Children[0].Visual);
- Assert.Empty(moveToNode.Children);
-
- moveFrom.Child = null;
- moveTo.Child = moveMe;
- tree.LayoutManager.ExecuteLayoutPass();
-
- scene = scene.CloneScene();
- moveFromNode = (VisualNode)scene.FindNode(moveFrom);
- moveToNode = (VisualNode)scene.FindNode(moveTo);
-
- moveFromNode.SortChildren(scene);
- moveToNode.SortChildren(scene);
- sceneBuilder.Update(scene, moveFrom);
- sceneBuilder.Update(scene, moveTo);
- sceneBuilder.Update(scene, moveMe);
-
- Assert.Empty(moveFromNode.Children);
- Assert.Equal(1, moveToNode.Children.Count);
- Assert.Same(moveMe, moveToNode.Children[0].Visual);
- }
- }
-
- [Fact]
- public void Should_Update_When_Control_Made_Invisible()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = decorator = new Decorator
- {
- Child = border = new Border
- {
- Background = Brushes.Red,
- Child = canvas = new Canvas(),
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var initial = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(initial);
-
- border.IsVisible = false;
- var result = initial.CloneScene();
-
- Assert.True(sceneBuilder.Update(result, border));
- Assert.False(sceneBuilder.Update(result, canvas));
-
- var decoratorNode = (VisualNode)result.Root.Children[0];
- Assert.Equal(0, decoratorNode.Children.Count);
-
- Assert.Null(result.FindNode(border));
- Assert.Null(result.FindNode(canvas));
- Assert.Equal(new Rect(0, 0, 100, 100), result.Layers.Single().Dirty.Single());
- }
- }
-
- [Fact]
- public void Should_Not_Dispose_Active_VisualNode_When_Control_Reparented_And_Child_Made_Invisible()
- {
- // Issue #3115
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- StackPanel panel;
- Border border1;
- Border border2;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = panel = new StackPanel
- {
- Children =
- {
- (border1 = new Border
- {
- Background = Brushes.Red,
- }),
- (border2 = new Border
- {
- Background = Brushes.Green,
- }),
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var decorator = new Decorator();
- tree.Child = null;
- decorator.Child = panel;
- tree.Child = decorator;
- border1.IsVisible = false;
-
- scene = scene.CloneScene();
- sceneBuilder.Update(scene, decorator);
-
- var panelNode = (VisualNode)scene.FindNode(panel);
- Assert.Equal(2, panelNode.Children.Count);
- Assert.False(panelNode.Children[0].Disposed);
- Assert.False(panelNode.Children[1].Disposed);
- }
- }
-
- [Fact]
- public void Should_Update_ClipBounds_For_Negative_Margin()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = decorator = new Decorator
- {
- Margin = new Thickness(0, 10, 0, 0),
- Child = border = new Border
- {
- Background = Brushes.Red,
- ClipToBounds = true,
- Margin = new Thickness(0, -5, 0, 0),
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var borderNode = scene.FindNode(border);
- Assert.Equal(new Rect(0, 5, 100, 95), borderNode.ClipBounds);
-
- border.Margin = new Thickness(0, -8, 0, 0);
- layout.ExecuteLayoutPass();
-
- scene = scene.CloneScene();
- sceneBuilder.Update(scene, border);
-
- borderNode = scene.FindNode(border);
- Assert.Equal(new Rect(0, 2, 100, 98), borderNode.ClipBounds);
- }
- }
-
- [Fact]
- public void Should_Update_Descendent_Tranform_When_Margin_Changed()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = decorator = new Decorator
- {
- Margin = new Thickness(0, 10, 0, 0),
- Child = border = new Border
- {
- Child = canvas = new Canvas(),
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var borderNode = scene.FindNode(border);
- var canvasNode = scene.FindNode(canvas);
- Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform);
- Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform);
-
- decorator.Margin = new Thickness(0, 20, 0, 0);
- layout.ExecuteLayoutPass();
-
- scene = scene.CloneScene();
- sceneBuilder.Update(scene, decorator);
-
- borderNode = scene.FindNode(border);
- canvasNode = scene.FindNode(canvas);
- Assert.Equal(Matrix.CreateTranslation(0, 20), borderNode.Transform);
- Assert.Equal(Matrix.CreateTranslation(0, 20), canvasNode.Transform);
- }
- }
-
- [Fact]
- public void DirtyRects_Should_Contain_Old_And_New_Bounds_When_Margin_Changed()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Width = 100,
- Height = 100,
- Child = decorator = new Decorator
- {
- Margin = new Thickness(0, 10, 0, 0),
- Child = border = new Border
- {
- Background = Brushes.Red,
- Child = canvas = new Canvas(),
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var borderNode = scene.FindNode(border);
- var canvasNode = scene.FindNode(canvas);
- Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform);
- Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform);
-
- decorator.Margin = new Thickness(0, 20, 0, 0);
- layout.ExecuteLayoutPass();
-
- scene = scene.CloneScene();
-
- sceneBuilder.Update(scene, decorator);
-
- var rects = scene.Layers.Single().Dirty.ToArray();
- Assert.Equal(new[] { new Rect(0, 10, 100, 90) }, rects);
- }
- }
-
- [Fact]
- public void Resizing_Scene_Should_Add_DirtyRects()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- ClientSize = new Size(100, 100),
- Child = decorator = new Decorator
- {
- Margin = new Thickness(0, 10, 0, 0),
- Child = border = new Border
- {
- Background = Brushes.Red,
- Child = canvas = new Canvas(),
- }
- }
- };
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- Assert.Equal(new Size(100, 100), scene.Size);
-
- tree.ClientSize = new Size(110, 120);
- scene = scene.CloneScene();
- sceneBuilder.Update(scene, tree);
-
- Assert.Equal(new Size(110, 120), scene.Size);
-
- var expected = new[]
- {
- new Rect(100, 0, 10, 100),
- new Rect(0, 100, 110, 20),
- };
-
- Assert.Equal(expected, scene.Layers[tree].Dirty.ToArray());
-
- // Layers are disabled. See #2244
- // Assert.Equal(expected, scene.Layers[border].Dirty.ToArray());
- }
- }
-
- [Fact]
- public void Setting_Opacity_Should_Add_Descendent_Bounds_To_DirtyRects()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- var tree = new TestRoot
- {
- Child = decorator = new Decorator
- {
- Child = border = new Border
- {
- Background = Brushes.Red,
- Width = 100,
- Height = 100,
- }
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(tree.DesiredSize));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- decorator.Opacity = 0.5;
- scene = scene.CloneScene();
- sceneBuilder.Update(scene, decorator);
-
- Assert.NotEmpty(scene.Layers.Single().Dirty);
- var dirty = scene.Layers.Single().Dirty.Single();
- Assert.Equal(new Rect(0, 0, 100, 100), dirty);
- }
- }
-
- [Fact]
- public void Should_Set_GeometryClip()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var clip = StreamGeometry.Parse("M100,0 L0,100 100,100");
- Decorator decorator;
- var tree = new TestRoot
- {
- Child = decorator = new Decorator
- {
- Clip = clip,
- }
- };
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var decoratorNode = scene.FindNode(decorator);
- Assert.Same(clip.PlatformImpl, decoratorNode.GeometryClip);
- }
- }
-
- [Fact]
- public void Disposing_Scene_Releases_DrawOperation_References()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var bitmap = RefCountable.Create(Mock.Of(
- x => x.PixelSize == new PixelSize(100, 100) &&
- x.Dpi == new Vector(96, 96)));
-
- Image img;
- var tree = new TestRoot
- {
- Child = img = new Image
- {
- Source = new Bitmap(bitmap),
- Height = 100,
- Width = 100
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(new Size(100, 100)));
-
- Assert.Equal(2, bitmap.RefCount);
- IRef operation;
-
- using (var scene = new Scene(tree))
- {
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
- operation = scene.FindNode(img).DrawOperations[0];
- Assert.Equal(1, operation.RefCount);
-
- Assert.Equal(3, bitmap.RefCount);
- }
- Assert.Equal(0, operation.RefCount);
- Assert.Equal(2, bitmap.RefCount);
- }
- }
-
- [Fact]
- public void Replacing_Control_Releases_DrawOperation_Reference()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var bitmap = RefCountable.Create(Mock.Of(
- x => x.PixelSize == new PixelSize(100, 100) &&
- x.Dpi == new Vector(96, 96)));
-
- Image img;
- var tree = new TestRoot
- {
- Child = img = new Image
- {
- Source = new Bitmap(bitmap),
- Width = 100,
- Height = 100
- }
- };
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(new Size(100, 100)));
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var operation = scene.FindNode(img).DrawOperations[0];
-
- tree.Child = new Decorator();
-
- using (var result = scene.CloneScene())
- {
- sceneBuilder.Update(result, img);
- scene.Dispose();
-
- Assert.Equal(0, operation.RefCount);
- Assert.Equal(2, bitmap.RefCount);
- }
- }
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
deleted file mode 100644
index 48ddef8bf4..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
+++ /dev/null
@@ -1,258 +0,0 @@
-using System.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Controls;
-using Avalonia.Data;
-using Avalonia.Media;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.UnitTests;
-using Avalonia.VisualTree;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
-{
- public partial class SceneBuilderTests
- {
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Control_With_Animated_Opacity_And_Children_Should_Start_New_Layer()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Padding = new Thickness(10),
- Width = 100,
- Height = 120,
- Child = decorator = new Decorator
- {
- Padding = new Thickness(11),
- Child = border = new Border
- {
- Background = Brushes.Red,
- Padding = new Thickness(12),
- Child = canvas = new Canvas()
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var rootNode = (VisualNode)scene.Root;
- var borderNode = (VisualNode)scene.FindNode(border);
- var canvasNode = (VisualNode)scene.FindNode(canvas);
-
- Assert.Same(tree, rootNode.LayerRoot);
- Assert.Same(border, borderNode.LayerRoot);
- Assert.Same(border, canvasNode.LayerRoot);
- Assert.Equal(0.5, scene.Layers[border].Opacity);
-
- Assert.Equal(2, scene.Layers.Count());
- Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new Visual[] { tree, border }));
-
- animation.OnCompleted();
- scene = scene.CloneScene();
-
- sceneBuilder.Update(scene, border);
-
- rootNode = (VisualNode)scene.Root;
- borderNode = (VisualNode)scene.FindNode(border);
- canvasNode = (VisualNode)scene.FindNode(canvas);
-
- Assert.Same(tree, rootNode.LayerRoot);
- Assert.Same(tree, borderNode.LayerRoot);
- Assert.Same(tree, canvasNode.LayerRoot);
- Assert.Single(scene.Layers);
-
- var rootDirty = scene.Layers[tree].Dirty;
-
- Assert.Single(rootDirty);
- Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single());
- }
- }
-
- [Fact]
- public void Control_With_Animated_Opacity_And_No_Children_Should_Not_Start_New_Layer()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- var tree = new TestRoot
- {
- Padding = new Thickness(10),
- Width = 100,
- Height = 120,
- Child = decorator = new Decorator
- {
- Padding = new Thickness(11),
- Child = border = new Border
- {
- Background = Brushes.Red,
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- Assert.Single(scene.Layers);
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Padding = new Thickness(10),
- Width = 100,
- Height = 120,
- Child = decorator = new Decorator
- {
- Padding = new Thickness(11),
- Child = border = new Border
- {
- Background = Brushes.Red,
- Padding = new Thickness(12),
- Child = canvas = new Canvas
- {
- Children = { new TextBlock() },
- }
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
- canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- Assert.Equal(3, scene.Layers.Count);
-
- decorator.Child = null;
- scene = scene.CloneScene();
-
- sceneBuilder.Update(scene, border);
-
- Assert.Equal(1, scene.Layers.Count);
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void Hiding_Transparent_Control_Should_Remove_Layers()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- Decorator decorator;
- Border border;
- Canvas canvas;
- var tree = new TestRoot
- {
- Padding = new Thickness(10),
- Width = 100,
- Height = 120,
- Child = decorator = new Decorator
- {
- Padding = new Thickness(11),
- Child = border = new Border
- {
- Background = Brushes.Red,
- Padding = new Thickness(12),
- Child = canvas = new Canvas
- {
- Children = { new TextBlock() },
- }
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
- canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- Assert.Equal(3, scene.Layers.Count);
-
- border.IsVisible = false;
- scene = scene.CloneScene();
-
- sceneBuilder.Update(scene, border);
-
- Assert.Equal(1, scene.Layers.Count);
- }
- }
-
- [Fact(Skip = "Layers are disabled. See #2244")]
- public void GeometryClip_Should_Affect_Child_Layers()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var clip = StreamGeometry.Parse("M100,0 L0,100 100,100");
- Decorator decorator;
- Border border;
- var tree = new TestRoot
- {
- Child = decorator = new Decorator
- {
- Clip = clip,
- Margin = new Thickness(12, 16),
- Child = border = new Border
- {
- Opacity = 0.5,
- Child = new Canvas(),
- }
- }
- };
-
- var layout = tree.LayoutManager;
- layout.ExecuteInitialLayoutPass();
-
- var animation = new BehaviorSubject(0.5);
- border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- var borderLayer = scene.Layers[border];
- Assert.Equal(
- Matrix.CreateTranslation(12, 16),
- ((MockStreamGeometryImpl)borderLayer.GeometryClip).Transform);
- }
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs
deleted file mode 100644
index 7e515e7ef9..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Linq;
-using Avalonia.Controls;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.UnitTests;
-using Avalonia.VisualTree;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
-{
- public class SceneLayersTests
- {
- [Fact]
- public void Layers_Should_Be_Ordered()
- {
- Border border;
- Decorator decorator;
- var root = new TestRoot
- {
- Child = border = new Border
- {
- Child = decorator = new Decorator(),
- }
- };
-
- var target = new SceneLayers(root);
- target.Add(root);
- target.Add(decorator);
- target.Add(border);
-
- var result = target.Select(x => x.LayerRoot).ToArray();
-
- Assert.Equal(new Visual[] { root, border, decorator }, result);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs
deleted file mode 100644
index 18ff31f676..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Linq;
-using Avalonia.Controls;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.UnitTests;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
-{
- public class SceneTests
- {
- [Fact]
- public void Cloning_Scene_Should_Retain_Layers_But_Not_DirtyRects()
- {
- Decorator decorator;
- var tree = new TestRoot
- {
- Child = decorator = new Decorator(),
- };
-
- var scene = new Scene(tree);
- scene.Layers.Add(tree);
- scene.Layers.Add(decorator);
-
- scene.Layers[tree].Dirty.Add(new Rect(0, 0, 100, 100));
- scene.Layers[decorator].Dirty.Add(new Rect(0, 0, 50, 100));
-
- scene = scene.CloneScene();
- Assert.Equal(2, scene.Layers.Count());
- Assert.Empty(scene.Layers[0].Dirty);
- Assert.Empty(scene.Layers[1].Dirty);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs
deleted file mode 100644
index b6920dc381..0000000000
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Utilities;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
-{
- public class VisualNodeTests
- {
- [Fact]
- public void Empty_Children_Collections_Should_Be_Shared()
- {
- var node1 = new VisualNode(new Control(), null);
- var node2 = new VisualNode(new Control(), null);
-
- Assert.Same(node1.Children, node2.Children);
- }
-
- [Fact]
- public void Adding_Child_Should_Create_Collection()
- {
- var node = new VisualNode(new Control(), null);
- var collection = node.Children;
-
- node.AddChild(new VisualNode(new Border(), node));
-
- Assert.NotSame(collection, node.Children);
- }
-
- [Fact]
- public void Empty_DrawOperations_Collections_Should_Be_Shared()
- {
- var node1 = new VisualNode(new Control(), null);
- var node2 = new VisualNode(new Control(), null);
-
- Assert.Same(node1.DrawOperations, node2.DrawOperations);
- }
-
- [Fact]
- public void Adding_DrawOperation_Should_Create_Collection()
- {
- var node = new VisualNode(new Control(), null);
- var collection = node.DrawOperations;
-
- node.AddDrawOperation(RefCountable.Create(Mock.Of()));
-
- Assert.NotSame(collection, node.DrawOperations);
- }
-
- [Fact]
- public void Cloned_Nodes_Should_Share_DrawOperations_Collection()
- {
- var node1 = new VisualNode(new Control(), null);
- node1.AddDrawOperation(RefCountable.Create(Mock.Of()));
-
- var node2 = node1.Clone(null);
-
- Assert.Same(node1.DrawOperations, node2.DrawOperations);
- }
-
- [Fact]
- public void Adding_DrawOperation_To_Cloned_Node_Should_Create_New_Collection()
- {
- var node1 = new VisualNode(new Control(), null);
- var operation1 = RefCountable.Create(Mock.Of());
- node1.AddDrawOperation(operation1);
-
- var node2 = node1.Clone(null);
- var operation2 = RefCountable.Create(Mock.Of());
- node2.ReplaceDrawOperation(0, operation2);
-
- Assert.NotSame(node1.DrawOperations, node2.DrawOperations);
- Assert.Equal(1, node1.DrawOperations.Count);
- Assert.Equal(1, node2.DrawOperations.Count);
- Assert.Same(operation1.Item, node1.DrawOperations[0].Item);
- Assert.Same(operation2.Item, node2.DrawOperations[0].Item);
- }
-
- [Fact]
- public void DrawOperations_In_Cloned_Node_Are_Cloned()
- {
- var node1 = new VisualNode(new Control(), null);
- var operation1 = RefCountable.Create(Mock.Of());
- node1.AddDrawOperation(operation1);
-
- var node2 = node1.Clone(null);
- var operation2 = RefCountable.Create(Mock.Of());
- node2.AddDrawOperation(operation2);
-
- Assert.Same(node1.DrawOperations[0].Item, node2.DrawOperations[0].Item);
- Assert.NotSame(node1.DrawOperations[0], node2.DrawOperations[0]);
- }
-
- [Fact]
- public void SortChildren_Does_Not_Throw_On_Null_Children()
- {
- var node = new VisualNode(new Control(), null);
- var scene = new Scene(new Control());
-
- node.SortChildren(scene);
- }
-
- [Fact]
- public void TrimChildren_Should_Work_Correctly()
- {
- var parent = new VisualNode(new Control(), null);
- var child1 = new VisualNode(new Control(), parent);
- var child2 = new VisualNode(new Control(), parent);
- var child3 = new VisualNode(new Control(), parent);
-
- parent.AddChild(child1);
- parent.AddChild(child2);
- parent.AddChild(child3);
- parent.TrimChildren(2);
-
- Assert.Equal(2, parent.Children.Count);
- Assert.False(child1.Disposed);
- Assert.False(child2.Disposed);
- Assert.True(child3.Disposed);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTests.cs b/tests/Avalonia.Base.UnitTests/VisualTests.cs
index fb214a6b34..11bdc4bc68 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTests.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTests.cs
@@ -150,7 +150,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Attaching_To_Visual_Tree_Should_Invalidate_Visual()
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var child = new Decorator();
var root = new TestRoot
{
@@ -165,7 +165,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Detaching_From_Visual_Tree_Should_Invalidate_Visual()
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var child = new Decorator();
var root = new TestRoot
{
@@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests
public void Changing_ZIndex_Should_InvalidateVisual()
{
Canvas canvas1;
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var root = new TestRoot
{
Child = new StackPanel
@@ -331,7 +331,7 @@ namespace Avalonia.Base.UnitTests
{
Canvas canvas1;
StackPanel stackPanel;
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var root = new TestRoot
{
Child = stackPanel = new StackPanel
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 76c7fe97fc..481b98a0b2 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -91,6 +91,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat { get; }
public PixelFormat DefaultPixelFormat { get; }
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public IFontManagerImpl CreateFontManager()
{
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs b/tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs
deleted file mode 100644
index d8158fd673..0000000000
--- a/tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Controls;
-using Avalonia.Controls.Shapes;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.UnitTests;
-using Avalonia.VisualTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.VisualTree
-{
- public class TransformedBoundsTests
- {
- [Fact]
- public void Should_Track_Bounds()
- {
- using (UnitTestApplication.Start(TestServices.StyledWindow))
- {
- var control = default(Rectangle);
- var tree = new Decorator
- {
- Padding = new Thickness(10),
- Child = new Decorator
- {
- Padding = new Thickness(5),
- Child = control = new Rectangle
- {
- Width = 15,
- Height = 15,
- },
- }
- };
-
- var context = new DrawingContext(Mock.Of());
-
- tree.Measure(Size.Infinity);
- tree.Arrange(new Rect(0, 0, 100, 100));
- ImmediateRenderer.Render(tree, context, true);
-
- var track = control.GetObservable(Visual.TransformedBoundsProperty);
- var results = new List();
- track.Subscribe(results.Add);
-
- Assert.Equal(new Rect(0, 0, 15, 15), results[0].Value.Bounds);
- Assert.Equal(Matrix.CreateTranslation(42, 42), results[0].Value.Transform);
- }
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
index 0c516a0481..3f5cb9dd01 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
@@ -15,16 +15,14 @@ namespace Avalonia.Base.UnitTests.VisualTree
[Fact]
public void Should_Find_Control()
{
- using (TestApplication())
+ Border target;
+ using var services = new CompositorTestServices(new Size(200, 200))
{
- Border target;
- var root = new TestRoot
+ TopLevel =
{
- Width = 200,
- Height = 200,
- Child = new StackPanel
+ Content = new StackPanel
{
- Background = Brushes.White,
+ Background = null,
Children =
{
(target = new Border
@@ -42,29 +40,25 @@ namespace Avalonia.Base.UnitTests.VisualTree
},
Orientation = Orientation.Horizontal,
}
- };
+ }
+ };
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
+ services.RunJobs();
+ var result = target.GetVisualsAt(new Point(50, 50));
- var result = target.GetVisualsAt(new Point(50, 50));
+ Assert.Same(target, result.Single());
- Assert.Same(target, result.Single());
- }
}
[Fact]
public void Should_Not_Find_Sibling_Control()
{
- using (TestApplication())
+ Border target;
+ using var services = new CompositorTestServices(new Size(200, 200))
{
- Border target;
- var root = new TestRoot
+ TopLevel =
{
- Width = 200,
- Height = 200,
- Child = new StackPanel
+ Content = new StackPanel
{
Background = Brushes.White,
Children =
@@ -84,21 +78,12 @@ namespace Avalonia.Base.UnitTests.VisualTree
},
Orientation = Orientation.Horizontal,
}
- };
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var result = target.GetVisualsAt(new Point(150, 50));
+ }
+ };
+ services.RunJobs();
+ var result = target.GetVisualsAt(new Point(150, 50));
- Assert.Empty(result);
- }
- }
-
- private static IDisposable TestApplication()
- {
- return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+ Assert.Empty(result);
}
}
}
diff --git a/tests/Avalonia.Benchmarks/NullRenderer.cs b/tests/Avalonia.Benchmarks/NullRenderer.cs
deleted file mode 100644
index feb325f630..0000000000
--- a/tests/Avalonia.Benchmarks/NullRenderer.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Avalonia.Rendering;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Benchmarks
-{
- internal class NullRenderer : IRenderer
- {
- public bool DrawFps { get; set; }
- public bool DrawDirtyRects { get; set; }
-#pragma warning disable CS0067
- public event EventHandler SceneInvalidated;
-#pragma warning restore CS0067
- public void AddDirty(Visual visual)
- {
- }
-
- public void Dispose()
- {
- }
-
- public IEnumerable HitTest(Point p, Visual root, Func filter) => null;
-
- public Visual HitTestFirst(Point p, Visual root, Func filter) => null;
-
- public void Paint(Rect rect)
- {
- }
-
- public void RecalculateChildren(Visual visual)
- {
- }
-
- public void Resized(Size size)
- {
- }
-
- public void Start()
- {
- }
-
- public void Stop()
- {
- }
-
- public ValueTask TryGetRenderInterfaceFeature(Type featureType) => new(0);
- }
-}
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 37b79855db..e5cbae4ae7 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -139,6 +139,8 @@ namespace Avalonia.Benchmarks
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
+
public void Dispose()
{
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 8bd51ec500..4ff98bdedd 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -134,16 +134,16 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Raises_Click()
{
- var renderer = Mock.Of();
+ var renderer = RendererMocks.CreateRenderer();
var pt = new Point(50, 50);
- Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
+ renderer.Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
var target = new TestButton()
{
Bounds = new Rect(0, 0, 100, 100),
- Renderer = renderer
+ Renderer = renderer.Object
};
bool clicked = false;
@@ -166,16 +166,16 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{
- var renderer = Mock.Of();
-
- Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
+ var renderer = RendererMocks.CreateRenderer();
+
+ renderer.Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
var target = new TestButton()
{
Bounds = new Rect(0, 0, 100, 100),
- Renderer = renderer
+ Renderer = renderer.Object
};
bool clicked = false;
@@ -199,9 +199,9 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_With_RenderTransform_Raises_Click()
{
- var renderer = Mock.Of();
+ var renderer = RendererMocks.CreateRenderer();
var pt = new Point(150, 50);
- Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
+ renderer.Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new Visual[] { r } : new Visual[0]);
@@ -210,7 +210,7 @@ namespace Avalonia.Controls.UnitTests
{
Bounds = new Rect(0, 0, 100, 100),
RenderTransform = new TranslateTransform { X = 100, Y = 0 },
- Renderer = renderer
+ Renderer = renderer.Object
};
//actual bounds of button should be 100,0,100,100 x -> translated 100 pixels
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index baf933bd66..d99c90cb77 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -595,7 +595,7 @@ namespace Avalonia.Controls.UnitTests
private static Window PreparedWindow(object content = null)
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var platform = AvaloniaLocator.Current.GetRequiredService();
var windowImpl = Mock.Get(platform.CreateWindow());
windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object);
diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
index 3a2e1c08bd..8cd5816984 100644
--- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
+using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@@ -189,6 +190,8 @@ namespace Avalonia.Controls.UnitTests
public void Impl_Closing_Should_Remove_Window_From_OpenWindows()
{
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
index 02767a21eb..7767de11c7 100644
--- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
@@ -569,7 +569,7 @@ namespace Avalonia.Controls.UnitTests
private static Window PreparedWindow(object content = null)
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var platform = AvaloniaLocator.Current.GetRequiredService();
var windowImpl = Mock.Get(platform.CreateWindow());
windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object);
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index f4206959a9..4804b29fee 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -563,7 +563,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
using (CreateServices())
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var platform = AvaloniaLocator.Current.GetRequiredService();
var windowImpl = Mock.Get(platform.CreateWindow());
windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object);
diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
index 8f9af52ed8..a10b1324d6 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
@@ -110,6 +110,8 @@ namespace Avalonia.Controls.UnitTests
public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close()
{
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.SetupProperty(x => x.Closed);
@@ -129,6 +131,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_IsVisible_True_Shows_Window()
{
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
@@ -145,6 +149,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_IsVisible_False_Hides_Window()
{
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
@@ -163,7 +169,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var target = new TestWindowBase(renderer.Object);
target.Show();
@@ -194,7 +200,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var target = new TestWindowBase(renderer.Object);
target.Show();
@@ -209,7 +215,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var windowImpl = new Mock();
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
@@ -240,12 +246,15 @@ namespace Avalonia.Controls.UnitTests
public bool IsClosed { get; private set; }
public TestWindowBase(IRenderer renderer = null)
- : base(Mock.Of(x =>
- x.RenderScaling == 1 &&
- x.CreateRenderer(It.IsAny()) == renderer))
+ : base(CreateWindowsBaseImplMock(renderer ?? RendererMocks.CreateRenderer().Object))
{
}
+ private static IWindowBaseImpl CreateWindowsBaseImplMock(IRenderer renderer)
+ => Mock.Of(x =>
+ x.RenderScaling == 1 &&
+ x.CreateRenderer(It.IsAny()) == renderer);
+
public TestWindowBase(IWindowBaseImpl impl)
: base(impl)
{
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index ca245005c2..014174990e 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -98,6 +98,8 @@ namespace Avalonia.Controls.UnitTests
public void IsVisible_Should_Be_False_After_Impl_Signals_Close()
{
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
@@ -269,7 +271,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var target = new Window(CreateImpl(renderer));
target.Show();
@@ -284,7 +286,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var target = new Window(CreateImpl(renderer));
parent.Show();
@@ -317,7 +319,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
var target = new Window(CreateImpl(renderer));
target.Show();
@@ -334,6 +336,8 @@ namespace Avalonia.Controls.UnitTests
{
var parent = new Window();
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
@@ -375,6 +379,8 @@ namespace Avalonia.Controls.UnitTests
{
var parent = new Window();
var windowImpl = new Mock();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
diff --git a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj
index 4a90da77e7..a99421f107 100644
--- a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj
+++ b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj
@@ -1,6 +1,7 @@
net6.0
+ true
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index ca08f3bcb2..c9f79871c9 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -462,9 +462,10 @@ namespace Avalonia.LeakTests
{
using (Start())
{
- var renderer = new Mock();
+ var renderer = RendererMocks.CreateRenderer();
renderer.Setup(x => x.Dispose());
var impl = new Mock();
+ impl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null);
impl.SetupGet(x => x.RenderScaling).Returns(1);
impl.SetupProperty(x => x.Closed);
impl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object);
@@ -1028,46 +1029,5 @@ namespace Avalonia.LeakTests
public IEnumerable Children { get; set; }
}
- private class NullRenderer : IRenderer
- {
- public bool DrawFps { get; set; }
- public bool DrawDirtyRects { get; set; }
-#pragma warning disable CS0067
- public event EventHandler SceneInvalidated;
-#pragma warning restore CS0067
- public void AddDirty(Visual visual)
- {
- }
-
- public void Dispose()
- {
- }
-
- public IEnumerable HitTest(Point p, Visual root, Func filter) => null;
-
- public Visual HitTestFirst(Point p, Visual root, Func filter) => null;
-
- public void Paint(Rect rect)
- {
- }
-
- public void RecalculateChildren(Visual visual)
- {
- }
-
- public void Resized(Size size)
- {
- }
-
- public void Start()
- {
- }
-
- public void Stop()
- {
- }
-
- public ValueTask TryGetRenderInterfaceFeature(Type featureType) => new(null);
- }
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 16b8bb3f91..0b33cd9d97 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
@@ -7,6 +8,7 @@ using System.Linq;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
@@ -550,6 +552,98 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal(dataContext.ListProperty[0], (string)((ContentPresenter)target.Presenter.Panel.Children[0]).Content);
}
}
+
+ [Fact]
+ public void InfersDataTemplateTypeFromParentDataGridItemsType()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+
+
+
+
+
+
+
+
+
+
+");
+ var target = window.FindControl("target");
+ var column = target!.Columns.Single();
+
+ var dataContext = new TestDataContext();
+
+ dataContext.ListProperty.Add("Test");
+
+ window.DataContext = dataContext;
+
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+
+ // Assert DataGridLikeColumn.Binding data type.
+ var compiledPath = ((CompiledBindingExtension)column.Binding).Path;
+ var node = Assert.IsType(Assert.Single(compiledPath.Elements));
+ Assert.Equal(typeof(int), node.Property.PropertyType);
+
+ // Assert DataGridLikeColumn.Template data type by evaluating the template.
+ var firstItem = dataContext.ListProperty[0];
+ var textBlockFromTemplate = (TextBlock)column.Template.Build(firstItem);
+ textBlockFromTemplate.DataContext = firstItem;
+ Assert.Equal(firstItem.Length.ToString(), textBlockFromTemplate.Text);
+ }
+ }
+
+ [Fact]
+ public void ExplicitDataTypeStillWorksOnDataGridLikeControls()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+
+
+
+
+
+
+
+
+
+
+");
+ var target = window.FindControl("target");
+ var column = target!.Columns.Single();
+
+ var dataContext = new TestDataContext();
+ dataContext.ListProperty.Add("Test");
+ target.Items = dataContext.ListProperty;
+
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+
+ // Assert DataGridLikeColumn.Binding data type.
+ var compiledPath = ((CompiledBindingExtension)column.Binding).Path;
+ var node = Assert.IsType(Assert.Single(compiledPath.Elements));
+ Assert.Equal(typeof(int), node.Property.PropertyType);
+
+ // Assert DataGridLikeColumn.Template data type by evaluating the template.
+ var firstItem = dataContext.ListProperty[0];
+ var textBlockFromTemplate = (TextBlock)column.Template.Build(firstItem);
+ textBlockFromTemplate.DataContext = firstItem;
+ Assert.Equal(firstItem.Length.ToString(), textBlockFromTemplate.Text);
+ }
+ }
[Fact]
public void ThrowsOnUninferrableDataTemplateInItemsControlWithoutItemsBinding()
@@ -1835,4 +1929,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
[AssignBinding] public IBinding X { get; set; }
}
+
+ public class DataGridLikeControl : Control
+ {
+ public static readonly DirectProperty ItemsProperty =
+ ItemsControl.ItemsProperty.AddOwner(o => o.Items, (o, v) => o.Items = v);
+
+ private IEnumerable _items;
+ public IEnumerable Items
+ {
+ get { return _items; }
+ set { SetAndRaise(ItemsProperty, ref _items, value); }
+ }
+
+ public AvaloniaList Columns { get; } = new();
+ }
+
+ public class DataGridLikeColumn
+ {
+ [AssignBinding]
+ [InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))]
+ public IBinding Binding { get; set; }
+
+ [InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))]
+ public IDataTemplate Template { get; set; }
+ }
}
diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
index c833017212..c0159aecff 100644
--- a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
+++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
@@ -68,6 +68,6 @@ public class AdornerTests : TestBase
tree.Arrange(new Rect(size));
await RenderToFile(tree);
- CompareImages(skipImmediate: true, skipDeferred: true);
+ CompareImages(skipImmediate: true);
}
}
\ No newline at end of file
diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
index 2d83f5ce0f..c63a876d81 100644
--- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs
+++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
@@ -9,6 +10,8 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Xunit;
+using Path = System.IO.Path;
+#pragma warning disable CS0649
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
@@ -60,13 +63,14 @@ namespace Avalonia.Direct2D1.RenderTests.Media
[Theory]
- [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888),
+ [InlineData(PixelFormatEnum.Rgba8888), InlineData(PixelFormatEnum.Bgra8888),
#if AVALONIA_SKIA
- InlineData(PixelFormat.Rgb565)
+ InlineData(PixelFormatEnum.Rgb565)
#endif
]
- public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
+ internal void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormatEnum fmte)
{
+ var fmt = new PixelFormat(fmte);
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, new PixelSize(80, 80));
var r = Avalonia.AvaloniaLocator.Current.GetRequiredService();
@@ -100,9 +104,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media
}
[Theory]
- [InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)]
- public void WriteableBitmapShouldBeUsable(PixelFormat fmt)
+ [InlineData(PixelFormatEnum.Bgra8888), InlineData(PixelFormatEnum.Rgba8888)]
+ internal void WriteableBitmapShouldBeUsable(PixelFormatEnum fmte)
{
+ var fmt = new PixelFormat(fmte);
var writeableBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), fmt);
var data = new int[256 * 256];
@@ -126,5 +131,122 @@ namespace Avalonia.Direct2D1.RenderTests.Media
CompareImagesNoRenderer(name);
}
+
+ struct RawHeader
+ {
+ public int Width, Height, Stride;
+ }
+
+ [Theory,
+ InlineData(PixelFormatEnum.BlackWhite),
+ InlineData(PixelFormatEnum.Gray2),
+ InlineData(PixelFormatEnum.Gray4),
+ InlineData(PixelFormatEnum.Gray8),
+ InlineData(PixelFormatEnum.Gray16),
+ InlineData(PixelFormatEnum.Gray32Float),
+ InlineData(PixelFormatEnum.Rgba64),
+ InlineData(PixelFormatEnum.Rgba64, AlphaFormat.Premul),
+ ]
+ internal unsafe void BitmapsShouldSupportTranscoders_Lenna(PixelFormatEnum format, AlphaFormat alphaFormat = AlphaFormat.Unpremul)
+ {
+ var relativeFilesDir = "../../../PixelFormats/Lenna";
+ var filesDir = Path.Combine(OutputPath, relativeFilesDir);
+
+ var formatName = format.ToString();
+ if (alphaFormat == AlphaFormat.Premul)
+ formatName = "P" + formatName.ToLowerInvariant();
+
+ var bitsData = File.ReadAllBytes(Path.Combine(filesDir, formatName + ".bits")).AsSpan();
+ var header = MemoryMarshal.Cast(bitsData.Slice(0, Unsafe.SizeOf()))[0];
+ var data = bitsData.Slice(Unsafe.SizeOf());
+
+ var size = new PixelSize(header.Width, header.Height);
+ var stride = header.Stride;
+
+ string expectedName = Path.Combine(relativeFilesDir, formatName);
+ if (!File.Exists(Path.Combine(OutputPath, expectedName + ".expected.png")))
+ expectedName = Path.Combine(relativeFilesDir, "Default");
+
+ var names = new[]
+ {
+ "_Writeable",
+ "_WriteableInitialized",
+ "_Normal"
+ };
+
+ foreach (var step in new[] { 0,1,2 })
+ {
+
+ var testName = nameof(BitmapsShouldSupportTranscoders_Lenna) + "_" + formatName + names[step];
+
+ var path = System.IO.Path.Combine(OutputPath, testName + ".out.png");
+ fixed (byte* pData = data)
+ {
+ Bitmap? b = null;
+ try
+ {
+ if (step == 0)
+ {
+ var bmp = new WriteableBitmap(size, new Vector(96, 96), new PixelFormat(format),
+ alphaFormat);
+
+ using (var l = bmp.Lock())
+ {
+ var minStride = (l.Size.Width * l.Format.BitsPerPixel + 7) / 8;
+ for (var y = 0; y < size.Height; y++)
+ {
+ Unsafe.CopyBlock((l.Address + y * l.RowBytes).ToPointer(), pData + y * stride,
+ (uint)minStride);
+ }
+ }
+
+ b = bmp;
+ }
+ else if (step == 1)
+ b = new WriteableBitmap(new PixelFormat(format), alphaFormat, new IntPtr(pData),
+ size, new Vector(96, 96), stride);
+ else
+ b = new Bitmap(new PixelFormat(format), alphaFormat, new IntPtr(pData),
+ size, new Vector(96, 96), stride);
+
+ if (step < 2)
+ {
+ var copyTo = new byte[data.Length];
+ fixed (byte* pCopyTo = copyTo)
+ b.CopyPixels(default, new IntPtr(pCopyTo), copyTo.Length, stride);
+ Assert.Equal(data.ToArray(), copyTo);
+ }
+
+ b.Save(path);
+ CompareImagesNoRenderer(testName, expectedName);
+ }
+ finally
+ {
+ b?.Dispose();
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public unsafe void CopyPixelsShouldWorkForNonTranscodedBitmaps()
+ {
+ var stride = 32 * 4;
+ var data = new byte[32 * stride];
+ new Random().NextBytes(data);
+ for (var c = 0; c < data.Length; c++)
+ if (data[c] == 0)
+ data[c] = 1;
+
+ Bitmap bmp;
+ fixed (byte* pData = data)
+ bmp = new Bitmap(PixelFormat.Bgra8888, AlphaFormat.Unpremul, new IntPtr(pData), new PixelSize(32, 32),
+ new Vector(96, 96), 32 * 4);
+
+ var copyTo = new byte[data.Length];
+ fixed (byte* pCopyTo = copyTo)
+ bmp.CopyPixels(default, new IntPtr(pCopyTo), data.Length, stride);
+ Assert.Equal(data, copyTo);
+ }
}
}
diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs
index edde62f041..af4500e87f 100644
--- a/tests/Avalonia.RenderTests/TestBase.cs
+++ b/tests/Avalonia.RenderTests/TestBase.cs
@@ -89,7 +89,6 @@ namespace Avalonia.Direct2D1.RenderTests
}
var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
- var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png");
var compositedPath = Path.Combine(OutputPath, testName + ".composited.out.png");
var factory = AvaloniaLocator.Current.GetRequiredService();
var pixelSize = new PixelSize((int)target.Width, (int)target.Height);
@@ -104,22 +103,6 @@ namespace Avalonia.Direct2D1.RenderTests
bitmap.Save(immediatePath);
}
-
- using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector))
- using (var renderer = new DeferredRenderer(target, rtb))
- {
- target.Measure(size);
- target.Arrange(new Rect(size));
- renderer.UnitTestUpdateScene();
-
- // Do the deferred render on a background thread to expose any threading errors in
- // the deferred rendering path.
- await Task.Run((Action)renderer.UnitTestRender);
- threadingInterface.MainThread = Thread.CurrentThread;
-
- rtb.Save(deferredPath);
- }
-
var timer = new ManualRenderTimer();
var compositor = new Compositor(new RenderLoop(timer, Dispatcher.UIThread), null);
@@ -157,20 +140,17 @@ namespace Avalonia.Direct2D1.RenderTests
}
protected void CompareImages([CallerMemberName] string testName = "",
- bool skipImmediate = false, bool skipDeferred = false, bool skipCompositor = false)
+ bool skipImmediate = false, bool skipCompositor = false)
{
var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
- var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png");
var compositedPath = Path.Combine(OutputPath, testName + ".composited.out.png");
using (var expected = Image.Load(expectedPath))
using (var immediate = Image.Load(immediatePath))
- using (var deferred = Image.Load(deferredPath))
using (var composited = Image.Load(compositedPath))
{
var immediateError = CompareImages(immediate, expected);
- var deferredError = CompareImages(deferred, expected);
var compositedError = CompareImages(composited, expected);
if (immediateError > 0.022 && !skipImmediate)
@@ -178,11 +158,6 @@ namespace Avalonia.Direct2D1.RenderTests
Assert.True(false, immediatePath + ": Error = " + immediateError);
}
- if (deferredError > 0.022 && !skipDeferred)
- {
- Assert.True(false, deferredPath + ": Error = " + deferredError);
- }
-
if (compositedError > 0.022 && !skipCompositor)
{
Assert.True(false, compositedPath + ": Error = " + compositedError);
@@ -190,9 +165,9 @@ namespace Avalonia.Direct2D1.RenderTests
}
}
- protected void CompareImagesNoRenderer([CallerMemberName] string testName = "")
+ protected void CompareImagesNoRenderer([CallerMemberName] string testName = "", string expectedName = null)
{
- var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
+ var expectedPath = Path.Combine(OutputPath, (expectedName ?? testName) + ".expected.png");
var actualPath = Path.Combine(OutputPath, testName + ".out.png");
using (var expected = Image.Load(expectedPath))
diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
index 5e481f21c1..ba45bbbc2e 100644
--- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
+++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
@@ -2,6 +2,7 @@
net6.0
AVALONIA_SKIA;AVALONIA_SKIA_SKIP_FAIL
+ true
diff --git a/tests/Avalonia.Skia.UnitTests/HitTesting.cs b/tests/Avalonia.Skia.UnitTests/HitTesting.cs
index df267ee136..2c9e4f587f 100644
--- a/tests/Avalonia.Skia.UnitTests/HitTesting.cs
+++ b/tests/Avalonia.Skia.UnitTests/HitTesting.cs
@@ -1,6 +1,8 @@
-using Avalonia.Controls.Shapes;
+using System;
+using Avalonia.Controls.Shapes;
using Avalonia.Layout;
using Avalonia.Media;
+using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Xunit;
@@ -16,29 +18,24 @@ namespace Avalonia.Skia.UnitTests
{
SkiaPlatform.Initialize();
- var root = new TestRoot
+ using var services = new CompositorTestServices(new Size(100, 100),
+ AvaloniaLocator.Current.GetRequiredService())
{
- Width = 100,
- Height = 100,
- Child = new Ellipse
+ TopLevel =
{
- Width = 100,
- Height = 100,
- Fill = Brushes.Red,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
+ Content = new Ellipse
+ {
+ Width = 100,
+ Height = 100,
+ Fill = Brushes.Red,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var outsideResult = root.Renderer.HitTest(new Point(10, 10), root, null);
- var insideResult = root.Renderer.HitTest(new Point(50, 50), root, null);
-
- Assert.Empty(outsideResult);
- Assert.Equal(new[] { root.Child }, insideResult);
+ services.AssertHitTest(10, 10, null, Array.Empty());
+ services.AssertHitTest(50, 50, null, services.TopLevel.Content);
}
}
@@ -49,30 +46,26 @@ namespace Avalonia.Skia.UnitTests
{
SkiaPlatform.Initialize();
- var root = new TestRoot
+ using var services = new CompositorTestServices(new Size(100, 100),
+ AvaloniaLocator.Current.GetRequiredService())
{
- Width = 100,
- Height = 100,
- Child = new Ellipse
+ TopLevel =
{
- Width = 100,
- Height = 100,
- Stroke = Brushes.Red,
- StrokeThickness = 5,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
+ Content = new Ellipse
+ {
+ Width = 100,
+ Height = 100,
+ Stroke = Brushes.Red,
+ StrokeThickness = 5,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
}
};
-
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
- root.Measure(Size.Infinity);
- root.Arrange(new Rect(root.DesiredSize));
-
- var outsideResult = root.Renderer.HitTest(new Point(50, 50), root, null);
- var insideResult = root.Renderer.HitTest(new Point(1, 50), root, null);
-
- Assert.Empty(outsideResult);
- Assert.Equal(new[] { root.Child }, insideResult);
+
+
+ services.AssertHitTest(50, 50, null, Array.Empty());
+ services.AssertHitTest(1, 50, null, services.TopLevel.Content);
}
}
}
diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
index 17448ade76..aec6647226 100644
--- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
+++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
@@ -1,11 +1,9 @@
- netstandard2.0
+ netstandard2.0
false
Library
false
- ..\..\build\avalonia.snk
- false
true
@@ -20,6 +18,7 @@
-
+
+
diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs
new file mode 100644
index 0000000000..5ef09a4d0f
--- /dev/null
+++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs
@@ -0,0 +1,209 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.Embedding;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Threading;
+using Xunit;
+
+namespace Avalonia.UnitTests;
+public class CompositorTestServices : IDisposable
+{
+ private readonly IDisposable _app;
+ public Compositor Compositor { get; }
+ public ManualRenderTimer Timer { get; } = new();
+ public EmbeddableControlRoot TopLevel { get; }
+ public CompositingRenderer Renderer { get; } = null!;
+ public DebugEvents Events { get; } = new();
+
+ public void Dispose()
+ {
+ TopLevel.Renderer.Stop();
+ TopLevel.Dispose();
+ _app.Dispose();
+ }
+
+ public CompositorTestServices(Size? size = null, IPlatformRenderInterface renderInterface = null)
+ {
+ var services = TestServices.MockPlatformRenderInterface;
+ if (renderInterface != null)
+ services = services.With(renderInterface: renderInterface);
+
+ _app = UnitTestApplication.Start(services);
+ try
+ {
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(Timer);
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new RenderLoop(Timer, Dispatcher.UIThread));
+
+ Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null);
+ var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000));
+ TopLevel = new EmbeddableControlRoot(impl)
+ {
+ Template = new FuncControlTemplate((parent, scope) =>
+ {
+ var presenter = new ContentPresenter
+ {
+ [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
+ };
+ scope.Register("PART_ContentPresenter", presenter);
+ return presenter;
+ })
+ };
+ Renderer = impl.Renderer;
+ TopLevel.Prepare();
+ TopLevel.Renderer.Start();
+ RunJobs();
+ Renderer.CompositionTarget.Server.DebugEvents = Events;
+ }
+ catch
+ {
+ _app.Dispose();
+ throw;
+ }
+ }
+
+ public void RunJobs()
+ {
+ Dispatcher.UIThread.RunJobs();
+ Timer.TriggerTick();
+ Dispatcher.UIThread.RunJobs();
+ }
+
+ public void AssertRects(params Rect[] rects)
+ {
+ RunJobs();
+ var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
+ var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
+ Assert.Equal(toAssert, invalidated);
+ Events.Rects.Clear();
+ }
+
+ public void AssertHitTest(double x, double y, Func filter, params object[] expected)
+ => AssertHitTest(new Point(x, y), filter, expected);
+
+ public void AssertHitTest(Point pt, Func filter, params object[] expected)
+ {
+ RunJobs();
+ var tested = Renderer.HitTest(pt, TopLevel, filter);
+ Assert.Equal(expected, tested);
+ }
+
+ public void AssertHitTestFirst(Point pt, Func filter, object expected)
+ {
+ RunJobs();
+ var tested = Renderer.HitTest(pt, TopLevel, filter).First();
+ Assert.Equal(expected, tested);
+ }
+
+ public class DebugEvents : ICompositionTargetDebugEvents
+ {
+ public List Rects = new();
+
+ public void RectInvalidated(Rect rc)
+ {
+ Rects.Add(rc);
+ }
+
+ public void Reset()
+ {
+ Rects.Clear();
+ }
+ }
+
+ public class ManualRenderTimer : IRenderTimer
+ {
+ public event Action Tick;
+ public bool RunsInBackground => false;
+ public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero);
+ public Task TriggerBackgroundTick() => Task.Run(TriggerTick);
+ }
+
+ class TopLevelImpl : ITopLevelImpl
+ {
+ private readonly Compositor _compositor;
+ public CompositingRenderer Renderer { get; private set; }
+
+ public TopLevelImpl(Compositor compositor, Size clientSize)
+ {
+ ClientSize = clientSize;
+ _compositor = compositor;
+ }
+
+ public void Dispose()
+ {
+
+ }
+
+ public Size ClientSize { get; }
+ public Size? FrameSize { get; }
+ public double RenderScaling => 1;
+ public IEnumerable Surfaces { get; } = new[] { new DummyFramebufferSurface() };
+ public Action Input { get; set; }
+ public Action Paint { get; set; }
+ public Action Resized { get; set; }
+ public Action ScalingChanged { get; set; }
+ public Action TransparencyLevelChanged { get; set; }
+
+ class DummyFramebufferSurface : IFramebufferPlatformSurface
+ {
+ public ILockedFramebuffer Lock()
+ {
+ var ptr = Marshal.AllocHGlobal(128);
+ return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96),
+ PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr));
+ }
+ }
+
+ public IRenderer CreateRenderer(IRenderRoot root)
+ {
+ return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces);
+ }
+
+ public void Invalidate(Rect rect)
+ {
+ }
+
+ public void SetInputRoot(IInputRoot inputRoot)
+ {
+ }
+
+ public Point PointToClient(PixelPoint point) => default;
+
+ public PixelPoint PointToScreen(Point point) => new();
+
+ public void SetCursor(ICursorImpl cursor)
+ {
+ }
+
+ public Action Closed { get; set; }
+ public Action LostFocus { get; set; }
+ public IMouseDevice MouseDevice { get; } = new MouseDevice();
+ public IPopupImpl CreatePopup() => throw new NotImplementedException();
+
+ public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
+ {
+ }
+
+ public WindowTransparencyLevel TransparencyLevel { get; }
+
+ public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
+ {
+ }
+
+ public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
+
+ public object TryGetFeature(Type featureType) => null;
+ }
+}
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 93073faefb..fe84659038 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -182,6 +182,8 @@ namespace Avalonia.UnitTests
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
+
public void Dispose()
{
}
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index e30f0fa5f3..142a9cd8ee 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -1,6 +1,5 @@
using System;
using Avalonia.Controls.Primitives.PopupPositioning;
-using Avalonia.Input;
using Moq;
using Avalonia.Platform;
using Avalonia.Rendering;
@@ -28,6 +27,8 @@ namespace Avalonia.UnitTests
var clientSize = new Size(initialWidth, initialHeight);
windowImpl.SetupAllProperties();
+ windowImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
@@ -35,6 +36,8 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
+ windowImpl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null);
+
windowImpl.Setup(x => x.CreatePopup()).Returns(() =>
{
return CreatePopupMock(windowImpl.Object).Object;
@@ -90,11 +93,14 @@ namespace Avalonia.UnitTests
var positioner = new ManagedPopupPositioner(positionerHelper);
popupImpl.SetupAllProperties();
+ popupImpl.Setup(x => x.CreateRenderer(It.IsAny()))
+ .Returns(() => RendererMocks.CreateRenderer().Object);
popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
popupImpl.Setup(x => x.RenderScaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
+ popupImpl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null);
popupImpl.Setup(x => x.Dispose()).Callback(() =>
{
popupImpl.Object.Closed?.Invoke();
diff --git a/tests/Avalonia.UnitTests/NullRenderer.cs b/tests/Avalonia.UnitTests/NullRenderer.cs
new file mode 100644
index 0000000000..1b59aa30eb
--- /dev/null
+++ b/tests/Avalonia.UnitTests/NullRenderer.cs
@@ -0,0 +1,57 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Rendering;
+
+namespace Avalonia.UnitTests;
+
+public sealed class NullRenderer : IRenderer
+{
+ public RendererDiagnostics Diagnostics { get; } = new();
+
+ public event EventHandler? SceneInvalidated;
+
+ public NullRenderer()
+ {
+ }
+
+ public void AddDirty(Visual visual)
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public IEnumerable HitTest(Point p, Visual root, Func filter)
+ => Enumerable.Empty();
+
+ public Visual? HitTestFirst(Point p, Visual root, Func filter)
+ => null;
+
+ public void Paint(Rect rect)
+ {
+ }
+
+ public void RecalculateChildren(Visual visual)
+ {
+ }
+
+ public void Resized(Size size)
+ {
+ }
+
+ public void Start()
+ {
+ }
+
+ public void Stop()
+ {
+ }
+
+ public ValueTask TryGetRenderInterfaceFeature(Type featureType)
+ => new((object?) null);
+}
diff --git a/tests/Avalonia.UnitTests/RendererMocks.cs b/tests/Avalonia.UnitTests/RendererMocks.cs
new file mode 100644
index 0000000000..d4808a7556
--- /dev/null
+++ b/tests/Avalonia.UnitTests/RendererMocks.cs
@@ -0,0 +1,15 @@
+using Avalonia.Rendering;
+using Moq;
+
+namespace Avalonia.UnitTests
+{
+ public static class RendererMocks
+ {
+ public static Mock CreateRenderer()
+ {
+ var renderer = new Mock();
+ renderer.SetupGet(r => r.Diagnostics).Returns(new RendererDiagnostics());
+ return renderer;
+ }
+ }
+}
diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs
index 93c04057ef..875c5eb944 100644
--- a/tests/Avalonia.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.UnitTests/TestRoot.cs
@@ -1,9 +1,7 @@
-using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
-using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
@@ -18,7 +16,7 @@ namespace Avalonia.UnitTests
public TestRoot()
{
- Renderer = Mock.Of();
+ Renderer = RendererMocks.CreateRenderer().Object;
LayoutManager = new LayoutManager(this);
IsVisible = true;
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs
index 82626877d0..620f301565 100644
--- a/tests/Avalonia.UnitTests/UnitTestApplication.cs
+++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs
@@ -10,6 +10,7 @@ using System.Reactive.Disposables;
using System.Reactive.Concurrency;
using Avalonia.Input.Platform;
using Avalonia.Animation;
+using Avalonia.Media;
namespace Avalonia.UnitTests
{
@@ -69,6 +70,10 @@ namespace Avalonia.UnitTests
.Bind