diff --git a/.editorconfig b/.editorconfig
index 3620896f34..eac5870f96 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true
#Net Analyzer
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
+# CS0649: Field 'field' is never assigned to, and will always have its default value 'value'
+dotnet_diagnostic.CS0649.severity = error
+
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = suggestion
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/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs
index 7a27c53023..403dd6f23e 100644
--- a/src/Avalonia.Base/Input/DragEventArgs.cs
+++ b/src/Avalonia.Base/Input/DragEventArgs.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Interactivity;
+using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input
@@ -32,7 +33,9 @@ namespace Avalonia.Input
return point;
}
- internal DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")]
+ public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent)
{
Data = data;
diff --git a/src/Avalonia.Base/Input/GotFocusEventArgs.cs b/src/Avalonia.Base/Input/GotFocusEventArgs.cs
index f3de55ebae..8d15c3f9ec 100644
--- a/src/Avalonia.Base/Input/GotFocusEventArgs.cs
+++ b/src/Avalonia.Base/Input/GotFocusEventArgs.cs
@@ -7,19 +7,18 @@ namespace Avalonia.Input
///
public class GotFocusEventArgs : RoutedEventArgs
{
- internal GotFocusEventArgs()
+ public GotFocusEventArgs() : base(InputElement.GotFocusEvent)
{
-
}
///
/// Gets or sets a value indicating how the change in focus occurred.
///
- public NavigationMethod NavigationMethod { get; set; }
+ public NavigationMethod NavigationMethod { get; init; }
///
/// Gets or sets any key modifiers active at the time of focus.
///
- public KeyModifiers KeyModifiers { get; set; }
+ public KeyModifiers KeyModifiers { get; init; }
}
}
diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs
index 35fa549995..9fa097c4b1 100644
--- a/src/Avalonia.Base/Input/KeyEventArgs.cs
+++ b/src/Avalonia.Base/Input/KeyEventArgs.cs
@@ -5,15 +5,10 @@ namespace Avalonia.Input
{
public class KeyEventArgs : RoutedEventArgs
{
- public KeyEventArgs()
- {
+ public IKeyboardDevice? Device { get; init; }
- }
+ public Key Key { get; init; }
- public IKeyboardDevice? Device { get; set; }
-
- public Key Key { get; set; }
-
- public KeyModifiers KeyModifiers { get; set; }
+ public KeyModifiers KeyModifiers { get; init; }
}
}
diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs
index 26ff71a4e7..c46834fff4 100644
--- a/src/Avalonia.Base/Input/KeyboardDevice.cs
+++ b/src/Avalonia.Base/Input/KeyboardDevice.cs
@@ -156,7 +156,6 @@ namespace Avalonia.Input
interactive?.RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = method,
KeyModifiers = keyModifiers,
});
diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs
index af0fa83382..c405cdfacd 100644
--- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs
@@ -1,13 +1,17 @@
+using System;
using Avalonia.Interactivity;
+using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class PointerDeltaEventArgs : PointerEventArgs
{
- public Vector Delta { get; set; }
+ public Vector Delta { get; }
- internal PointerDeltaEventArgs(RoutedEvent routedEvent, object? source,
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0.")]
+ public PointerDeltaEventArgs(RoutedEvent routedEvent, object? source,
IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
: base(routedEvent, source, pointer, rootVisual, rootVisualPosition,
diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs
index d736253728..50d7cc5dc5 100644
--- a/src/Avalonia.Base/Input/PointerEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerEventArgs.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
+using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input
@@ -13,7 +14,9 @@ namespace Avalonia.Input
private readonly PointerPointProperties _properties;
private readonly Lazy?>? _previousPoints;
- internal PointerEventArgs(RoutedEvent routedEvent,
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
+ public PointerEventArgs(RoutedEvent routedEvent,
object? source,
IPointer pointer,
Visual? rootVisual, Point rootVisualPosition,
@@ -124,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,
@@ -143,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,
@@ -164,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/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs
index a3de0eaaea..903019d85d 100644
--- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs
@@ -1,13 +1,17 @@
+using System;
using Avalonia.Interactivity;
+using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class PointerWheelEventArgs : PointerEventArgs
{
- public Vector Delta { get; set; }
+ public Vector Delta { get; }
- internal PointerWheelEventArgs(object source, IPointer pointer, Visual rootVisual,
+ [Unstable]
+ [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow.MouseWheel.")]
+ public PointerWheelEventArgs(object source, IPointer pointer, Visual rootVisual,
Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
: base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition,
diff --git a/src/Avalonia.Base/Input/Raw/RawTextInputEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawTextInputEventArgs.cs
index 48c882197f..cd1cf29bcf 100644
--- a/src/Avalonia.Base/Input/Raw/RawTextInputEventArgs.cs
+++ b/src/Avalonia.Base/Input/Raw/RawTextInputEventArgs.cs
@@ -12,6 +12,6 @@ namespace Avalonia.Input.Raw
Text = text;
}
- public string Text { get; set; }
+ public string Text { get; }
}
}
diff --git a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs
index 55aaadff71..dd78080708 100644
--- a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs
+++ b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Input
public static int GetNextFreeId() => _nextId++;
- internal ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent)
+ public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent)
{
Id = id;
Delta = delta;
@@ -25,7 +25,7 @@ namespace Avalonia.Input
{
public int Id { get; }
- internal ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent)
+ public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent)
{
Id = id;
}
diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs
index 3e15c4843a..663207a104 100644
--- a/src/Avalonia.Base/Input/TappedEventArgs.cs
+++ b/src/Avalonia.Base/Input/TappedEventArgs.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@@ -7,7 +8,7 @@ namespace Avalonia.Input
{
private readonly PointerEventArgs lastPointerEventArgs;
- internal TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs)
+ public TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs)
: base(routedEvent)
{
this.lastPointerEventArgs = lastPointerEventArgs;
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/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs
index a027bec0c6..cda0103749 100644
--- a/src/Avalonia.Base/Input/TextInputEventArgs.cs
+++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs
@@ -4,10 +4,6 @@ namespace Avalonia.Input
{
public class TextInputEventArgs : RoutedEventArgs
{
- public TextInputEventArgs()
- {
-
- }
public IKeyboardDevice? Device { get; set; }
public string? Text { get; set; }
diff --git a/src/Avalonia.Base/Input/VectorEventArgs.cs b/src/Avalonia.Base/Input/VectorEventArgs.cs
index 3e8098f904..2ce95cf35a 100644
--- a/src/Avalonia.Base/Input/VectorEventArgs.cs
+++ b/src/Avalonia.Base/Input/VectorEventArgs.cs
@@ -5,11 +5,6 @@ namespace Avalonia.Input
{
public class VectorEventArgs : RoutedEventArgs
{
- internal VectorEventArgs()
- {
-
- }
-
- public Vector Vector { get; set; }
+ public Vector Vector { get; init; }
}
}
diff --git a/src/Avalonia.Base/Interactivity/CancelRoutedEventArgs.cs b/src/Avalonia.Base/Interactivity/CancelRoutedEventArgs.cs
new file mode 100644
index 0000000000..b6913939ab
--- /dev/null
+++ b/src/Avalonia.Base/Interactivity/CancelRoutedEventArgs.cs
@@ -0,0 +1,39 @@
+namespace Avalonia.Interactivity
+{
+ ///
+ /// Provides state information and data specific to a cancelable routed event.
+ ///
+ public class CancelRoutedEventArgs : RoutedEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CancelRoutedEventArgs()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The routed event associated with these event args.
+ public CancelRoutedEventArgs(RoutedEvent? routedEvent)
+ : base(routedEvent)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The routed event associated with these event args.
+ /// The source object that raised the routed event.
+ public CancelRoutedEventArgs(RoutedEvent? routedEvent, object? source)
+ : base(routedEvent, source)
+ {
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the routed event should be canceled.
+ ///
+ public bool Cancel { get; set; } = false;
+ }
+}
diff --git a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs
index 749d2ecc2b..1cdc775b13 100644
--- a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs
+++ b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Layout
///
public class EffectiveViewportChangedEventArgs : EventArgs
{
- internal EffectiveViewportChangedEventArgs(Rect effectiveViewport)
+ public EffectiveViewportChangedEventArgs(Rect effectiveViewport)
{
EffectiveViewport = effectiveViewport;
}
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/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
index cec20678cf..a4005d4f5f 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
@@ -1,50 +1,65 @@
using System;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security;
using System.Threading.Tasks;
-using Avalonia.Metadata;
namespace Avalonia.Platform.Storage.FileIO;
-[Unstable]
-public class BclStorageFile : IStorageBookmarkFile
+internal class BclStorageFile : IStorageBookmarkFile
{
- private readonly FileInfo _fileInfo;
-
public BclStorageFile(string fileName)
{
- _fileInfo = new FileInfo(fileName);
+ FileInfo = new FileInfo(fileName);
}
public BclStorageFile(FileInfo fileInfo)
{
- _fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo));
+ FileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo));
}
+ public FileInfo FileInfo { get; }
+
public bool CanOpenRead => true;
public bool CanOpenWrite => true;
- public string Name => _fileInfo.Name;
+ public string Name => FileInfo.Name;
public virtual bool CanBookmark => true;
+ public Uri Path
+ {
+ get
+ {
+ try
+ {
+ if (FileInfo.Directory is not null)
+ {
+ return StorageProviderHelpers.FilePathToUri(FileInfo.FullName);
+ }
+ }
+ catch (SecurityException)
+ {
+ }
+ return new Uri(FileInfo.Name, UriKind.Relative);
+ }
+ }
+
public Task GetBasicPropertiesAsync()
{
- if (_fileInfo.Exists)
+ if (FileInfo.Exists)
{
return Task.FromResult(new StorageItemProperties(
- (ulong)_fileInfo.Length,
- _fileInfo.CreationTimeUtc,
- _fileInfo.LastAccessTimeUtc));
+ (ulong)FileInfo.Length,
+ FileInfo.CreationTimeUtc,
+ FileInfo.LastAccessTimeUtc));
}
return Task.FromResult(new StorageItemProperties());
}
public Task GetParentAsync()
{
- if (_fileInfo.Directory is { } directory)
+ if (FileInfo.Directory is { } directory)
{
return Task.FromResult(new BclStorageFolder(directory));
}
@@ -53,17 +68,17 @@ public class BclStorageFile : IStorageBookmarkFile
public Task OpenReadAsync()
{
- return Task.FromResult(_fileInfo.OpenRead());
+ return Task.FromResult(FileInfo.OpenRead());
}
public Task OpenWriteAsync()
{
- return Task.FromResult(_fileInfo.OpenWrite());
+ return Task.FromResult(FileInfo.OpenWrite());
}
public virtual Task SaveBookmarkAsync()
{
- return Task.FromResult(_fileInfo.FullName);
+ return Task.FromResult(FileInfo.FullName);
}
public Task ReleaseBookmarkAsync()
@@ -72,28 +87,6 @@ public class BclStorageFile : IStorageBookmarkFile
return Task.CompletedTask;
}
- public bool TryGetUri([NotNullWhen(true)] out Uri? uri)
- {
- try
- {
- if (_fileInfo.Directory is not null)
- {
- uri = Path.IsPathRooted(_fileInfo.FullName) ?
- new Uri(new Uri("file://"), _fileInfo.FullName) :
- new Uri(_fileInfo.FullName, UriKind.Relative);
- return true;
- }
-
- uri = null;
- return false;
- }
- catch (SecurityException)
- {
- uri = null;
- return false;
- }
- }
-
protected virtual void Dispose(bool disposing)
{
}
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
index b91e910777..1e21c197bb 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
@@ -1,23 +1,18 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
-using Avalonia.Metadata;
namespace Avalonia.Platform.Storage.FileIO;
-[Unstable]
-public class BclStorageFolder : IStorageBookmarkFolder
+internal class BclStorageFolder : IStorageBookmarkFolder
{
- private readonly DirectoryInfo _directoryInfo;
-
public BclStorageFolder(string path)
{
- _directoryInfo = new DirectoryInfo(path);
- if (!_directoryInfo.Exists)
+ DirectoryInfo = new DirectoryInfo(path);
+ if (!DirectoryInfo.Exists)
{
throw new ArgumentException("Directory must exist");
}
@@ -25,29 +20,46 @@ public class BclStorageFolder : IStorageBookmarkFolder
public BclStorageFolder(DirectoryInfo directoryInfo)
{
- _directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo));
- if (!_directoryInfo.Exists)
+ DirectoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo));
+ if (!DirectoryInfo.Exists)
{
throw new ArgumentException("Directory must exist", nameof(directoryInfo));
}
}
- public string Name => _directoryInfo.Name;
+ public string Name => DirectoryInfo.Name;
+
+ public DirectoryInfo DirectoryInfo { get; }
public bool CanBookmark => true;
+ public Uri Path
+ {
+ get
+ {
+ try
+ {
+ return StorageProviderHelpers.FilePathToUri(DirectoryInfo.FullName);
+ }
+ catch (SecurityException)
+ {
+ return new Uri(DirectoryInfo.Name, UriKind.Relative);
+ }
+ }
+ }
+
public Task GetBasicPropertiesAsync()
{
var props = new StorageItemProperties(
null,
- _directoryInfo.CreationTimeUtc,
- _directoryInfo.LastAccessTimeUtc);
+ DirectoryInfo.CreationTimeUtc,
+ DirectoryInfo.LastAccessTimeUtc);
return Task.FromResult(props);
}
public Task GetParentAsync()
{
- if (_directoryInfo.Parent is { } directory)
+ if (DirectoryInfo.Parent is { } directory)
{
return Task.FromResult(new BclStorageFolder(directory));
}
@@ -56,9 +68,9 @@ public class BclStorageFolder : IStorageBookmarkFolder
public Task> GetItemsAsync()
{
- var items = _directoryInfo.GetDirectories()
+ var items = DirectoryInfo.GetDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d))
- .Concat(_directoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
+ .Concat(DirectoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
.ToArray();
return Task.FromResult>(items);
@@ -66,7 +78,7 @@ public class BclStorageFolder : IStorageBookmarkFolder
public virtual Task SaveBookmarkAsync()
{
- return Task.FromResult(_directoryInfo.FullName);
+ return Task.FromResult(DirectoryInfo.FullName);
}
public Task ReleaseBookmarkAsync()
@@ -75,23 +87,6 @@ public class BclStorageFolder : IStorageBookmarkFolder
return Task.CompletedTask;
}
- public bool TryGetUri([NotNullWhen(true)] out Uri? uri)
- {
- try
- {
- uri = Path.IsPathRooted(_directoryInfo.FullName) ?
- new Uri(new Uri("file://"), _directoryInfo.FullName) :
- new Uri(_directoryInfo.FullName, UriKind.Relative);
-
- return true;
- }
- catch (SecurityException)
- {
- uri = null;
- return false;
- }
- }
-
protected virtual void Dispose(bool disposing)
{
}
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
index 469388021e..ee169d62a5 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
@@ -1,12 +1,13 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
-using Avalonia.Metadata;
+using Avalonia.Compatibility;
namespace Avalonia.Platform.Storage.FileIO;
-[Unstable]
-public abstract class BclStorageProvider : IStorageProvider
+internal abstract class BclStorageProvider : IStorageProvider
{
public abstract bool CanOpen { get; }
public abstract Task> OpenFilePickerAsync(FilePickerOpenOptions options);
@@ -32,4 +33,98 @@ public abstract class BclStorageProvider : IStorageProvider
? Task.FromResult(new BclStorageFolder(folder))
: Task.FromResult(null);
}
+
+ public virtual Task TryGetFileFromPath(Uri filePath)
+ {
+ if (filePath.IsAbsoluteUri)
+ {
+ var file = new FileInfo(filePath.LocalPath);
+ if (file.Exists)
+ {
+ return Task.FromResult(new BclStorageFile(file));
+ }
+ }
+
+ return Task.FromResult(null);
+ }
+
+ public virtual Task TryGetFolderFromPath(Uri folderPath)
+ {
+ if (folderPath.IsAbsoluteUri)
+ {
+ var directory = new DirectoryInfo(folderPath.LocalPath);
+ if (directory.Exists)
+ {
+ return Task.FromResult(new BclStorageFolder(directory));
+ }
+ }
+
+ return Task.FromResult(null);
+ }
+
+ public virtual Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
+ {
+ // Note, this BCL API returns different values depending on the .NET version.
+ // We should also document it.
+ // https://github.com/dotnet/docs/issues/31423
+ // For pre-breaking change values see table:
+ // https://johnkoerner.com/csharp/special-folder-values-on-windows-versus-mac/
+ var folderPath = wellKnownFolder switch
+ {
+ WellKnownFolder.Desktop => GetFromSpecialFolder(Environment.SpecialFolder.Desktop),
+ WellKnownFolder.Documents => GetFromSpecialFolder(Environment.SpecialFolder.MyDocuments),
+ WellKnownFolder.Downloads => GetDownloadsWellKnownFolder(),
+ WellKnownFolder.Music => GetFromSpecialFolder(Environment.SpecialFolder.MyMusic),
+ WellKnownFolder.Pictures => GetFromSpecialFolder(Environment.SpecialFolder.MyPictures),
+ WellKnownFolder.Videos => GetFromSpecialFolder(Environment.SpecialFolder.MyVideos),
+ _ => throw new ArgumentOutOfRangeException(nameof(wellKnownFolder), wellKnownFolder, null)
+ };
+
+ if (folderPath is null)
+ {
+ return Task.FromResult(null);
+ }
+
+ var directory = new DirectoryInfo(folderPath);
+ if (!directory.Exists)
+ {
+ return Task.FromResult(null);
+ }
+
+ return Task.FromResult(new BclStorageFolder(directory));
+
+ string GetFromSpecialFolder(Environment.SpecialFolder folder) =>
+ Environment.GetFolderPath(folder, Environment.SpecialFolderOption.Create);
+ }
+
+ // TODO, replace with https://github.com/dotnet/runtime/issues/70484 when implemented.
+ // Normally we want to avoid platform specific code in the Avalonia.Base assembly.
+ protected static string? GetDownloadsWellKnownFolder()
+ {
+ if (OperatingSystemEx.IsWindows())
+ {
+ return Environment.OSVersion.Version.Major < 6 ? null :
+ SHGetKnownFolderPath(s_folderDownloads, 0, IntPtr.Zero);
+ }
+
+ if (OperatingSystemEx.IsLinux())
+ {
+ var envDir = Environment.GetEnvironmentVariable("XDG_DOWNLOAD_DIR");
+ if (envDir != null && Directory.Exists(envDir))
+ {
+ return envDir;
+ }
+ }
+
+ if (OperatingSystemEx.IsLinux() || OperatingSystemEx.IsMacOS())
+ {
+ return "~/Downloads";
+ }
+
+ return null;
+ }
+
+ private static readonly Guid s_folderDownloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
+ private static extern string SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid id, int flags, IntPtr token);
}
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
index f90d0a5a2f..55e84ee937 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
@@ -1,13 +1,23 @@
using System;
using System.IO;
using System.Linq;
-using Avalonia.Metadata;
+using System.Text;
namespace Avalonia.Platform.Storage.FileIO;
-[Unstable]
-public static class StorageProviderHelpers
+internal static class StorageProviderHelpers
{
+ public static Uri FilePathToUri(string path)
+ {
+ var uriPath = new StringBuilder(path)
+ .Replace("%", $"%{(int)'%':X2}")
+ .Replace("[", $"%{(int)'[':X2}")
+ .Replace("]", $"%{(int)']':X2}")
+ .ToString();
+
+ return new UriBuilder("file", string.Empty) { Path = uriPath }.Uri;
+ }
+
public static string NameWithExtension(string path, string? defaultExtension, FilePickerFileType? filter)
{
var name = Path.GetFileName(path);
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs
index f5469d31c9..c2c93400b0 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs
@@ -20,13 +20,13 @@ public interface IStorageItem : IDisposable
string Name { get; }
///
- /// Gets the full file-system path of the item, if the item has a path.
+ /// Gets the file-system path of the item.
///
///
/// Android backend might return file path with "content:" scheme.
/// Browser and iOS backends might return relative uris.
///
- bool TryGetUri([NotNullWhen(true)] out Uri? uri);
+ Uri Path { get; }
///
/// Gets the basic properties of the current item.
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs b/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
index 0f5cf931d9..6922151e02 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia.Metadata;
@@ -53,4 +55,35 @@ public interface IStorageProvider
/// Bookmark ID.
/// Bookmarked folder or null if OS denied request.
Task OpenFolderBookmarkAsync(string bookmark);
+
+ ///
+ /// Attempts to read file from the file-system by its path.
+ ///
+ /// The path of the item to retrieve in Uri format.
+ ///
+ /// Uri path is usually expected to be an absolute path with "file" scheme.
+ /// But it can be an uri with "content" scheme on the Android.
+ /// It also might ask user for the permission, and throw an exception if it was denied.
+ ///
+ /// File or null if it doesn't exist.
+ Task TryGetFileFromPath(Uri filePath);
+
+ ///
+ /// Attempts to read folder from the file-system by its path.
+ ///
+ /// The path of the folder to retrieve in Uri format.
+ ///
+ /// Uri path is usually expected to be an absolute path with "file" scheme.
+ /// But it can be an uri with "content" scheme on the Android.
+ /// It also might ask user for the permission, and throw an exception if it was denied.
+ ///
+ /// Folder or null if it doesn't exist.
+ Task TryGetFolderFromPath(Uri folderPath);
+
+ ///
+ /// Attempts to read folder from the file-system by its path
+ ///
+ /// Well known folder identifier.
+ /// Folder or null if it doesn't exist.
+ Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder);
}
diff --git a/src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs b/src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs
new file mode 100644
index 0000000000..4a0d0c634f
--- /dev/null
+++ b/src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs
@@ -0,0 +1,6 @@
+namespace Avalonia.Platform.Storage;
+
+public class NameCollisionOption
+{
+
+}
diff --git a/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs b/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
new file mode 100644
index 0000000000..c7772d1196
--- /dev/null
+++ b/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
@@ -0,0 +1,55 @@
+using System.Threading.Tasks;
+using Avalonia.Platform.Storage.FileIO;
+
+namespace Avalonia.Platform.Storage;
+
+///
+/// Group of public extensions for class.
+///
+public static class StorageProviderExtensions
+{
+ ///
+ public static Task TryGetFileFromPath(this IStorageProvider provider, string filePath)
+ {
+ return provider.TryGetFileFromPath(StorageProviderHelpers.FilePathToUri(filePath));
+ }
+
+ ///
+ public static Task TryGetFolderFromPath(this IStorageProvider provider, string folderPath)
+ {
+ return provider.TryGetFolderFromPath(StorageProviderHelpers.FilePathToUri(folderPath));
+ }
+
+ internal static string? TryGetFullPath(this IStorageFolder folder)
+ {
+ // We can avoid double escaping of the path by checking for BclStorageFolder.
+ // Ideally, `folder.Path.LocalPath` should also work, as that's only available way for the users.
+ if (folder is BclStorageFolder storageFolder)
+ {
+ return storageFolder.DirectoryInfo.FullName;
+ }
+
+ if (folder.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
+ {
+ return absolutePath.LocalPath;
+ }
+
+ // android "content:", browser and ios relative links go here.
+ return null;
+ }
+
+ internal static string? TryGetFullPath(this IStorageFile file)
+ {
+ if (file is BclStorageFile storageFolder)
+ {
+ return storageFolder.FileInfo.FullName;
+ }
+
+ if (file.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
+ {
+ return absolutePath.LocalPath;
+ }
+
+ return null;
+ }
+}
diff --git a/src/Avalonia.Base/Platform/Storage/WellKnownFolder.cs b/src/Avalonia.Base/Platform/Storage/WellKnownFolder.cs
new file mode 100644
index 0000000000..fcc933bc53
--- /dev/null
+++ b/src/Avalonia.Base/Platform/Storage/WellKnownFolder.cs
@@ -0,0 +1,37 @@
+namespace Avalonia.Platform.Storage;
+
+///
+/// Specifies enumerated constants used to retrieve directory paths to system well known folders.
+///
+public enum WellKnownFolder
+{
+ ///
+ /// Current user desktop folder.
+ ///
+ Desktop,
+
+ ///
+ /// Current user documents folder.
+ ///
+ Documents,
+
+ ///
+ /// Current user downloads folder.
+ ///
+ Downloads,
+
+ ///
+ /// Current user music folder
+ ///
+ Music,
+
+ ///
+ /// Current user pictures folder
+ ///
+ Pictures,
+
+ ///
+ /// Current user videos folder
+ ///
+ Videos,
+}
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..c5d7ec61e0 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -83,8 +83,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 +101,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 +265,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.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
index bdcbe65403..aea4df525d 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
@@ -37,6 +37,8 @@ namespace Avalonia.Rendering.Composition
private List _pendingServerCompositorJobs = new();
internal IEasing DefaultEasing { get; }
+
+ internal event Action? AfterCommit;
///
@@ -88,6 +90,7 @@ namespace Avalonia.Rendering.Composition
{
if (_invokeBeforeCommitWrite.Count > 0)
RequestCommitAsync();
+ AfterCommit?.Invoke();
}
}
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/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/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/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/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
index 73840376fe..cac4d1693a 100644
--- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
+++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Rendering
///
/// The render root that has been updated.
/// The updated area.
- internal SceneInvalidatedEventArgs(
+ public SceneInvalidatedEventArgs(
IRenderRoot root,
Rect dirtyRect)
{
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.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
index 2ccf20d460..8f52b059f1 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
@@ -245,11 +245,12 @@
VerticalAlignment="Center" />
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
index 286ca71bc2..fab5415bfb 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
@@ -494,11 +494,12 @@
VerticalAlignment="Center" />
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
index b82d36a288..d44902dd70 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
@@ -245,11 +245,12 @@
VerticalAlignment="Center" />
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
index 556099f56b..0ce6dd5e04 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
@@ -456,11 +456,12 @@
VerticalAlignment="Center" />
+
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/Expander.cs b/src/Avalonia.Controls/Expander.cs
index 65227a826a..2ad6a58d38 100644
--- a/src/Avalonia.Controls/Expander.cs
+++ b/src/Avalonia.Controls/Expander.cs
@@ -59,12 +59,11 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly DirectProperty IsExpandedProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly StyledProperty IsExpandedProperty =
+ AvaloniaProperty.Register(
nameof(IsExpanded),
- o => o.IsExpanded,
- (o, v) => o.IsExpanded = v,
- defaultBindingMode: Data.BindingMode.TwoWay);
+ defaultBindingMode: BindingMode.TwoWay,
+ coerce: CoerceIsExpanded);
///
/// Defines the event.
@@ -77,8 +76,8 @@ namespace Avalonia.Controls
///
/// Defines the event.
///
- public static readonly RoutedEvent CollapsingEvent =
- RoutedEvent.Register(
+ public static readonly RoutedEvent CollapsingEvent =
+ RoutedEvent.Register(
nameof(Collapsing),
RoutingStrategies.Bubble);
@@ -93,13 +92,12 @@ namespace Avalonia.Controls
///
/// Defines the event.
///
- public static readonly RoutedEvent ExpandingEvent =
- RoutedEvent.Register(
+ public static readonly RoutedEvent ExpandingEvent =
+ RoutedEvent.Register(
nameof(Expanding),
RoutingStrategies.Bubble);
private bool _ignorePropertyChanged = false;
- private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
///
@@ -134,50 +132,8 @@ namespace Avalonia.Controls
///
public bool IsExpanded
{
- get => _isExpanded;
- set
- {
- // It is important here that IsExpanded is a direct property so events can be invoked
- // BEFORE the property system gets notified of updated values. This is because events
- // may be canceled by external code.
- if (_isExpanded != value)
- {
- RoutedEventArgs eventArgs;
-
- if (value)
- {
- eventArgs = new RoutedEventArgs(ExpandingEvent, this);
- OnExpanding(eventArgs);
- }
- else
- {
- eventArgs = new RoutedEventArgs(CollapsingEvent, this);
- OnCollapsing(eventArgs);
- }
-
- if (eventArgs.Handled)
- {
- // If the event was externally handled (canceled) we must still notify the value has changed.
- // This property changed notification will update any external code observing this property that itself may have set the new value.
- // We are essentially reverted any external state change along with ignoring the IsExpanded property set.
- // Remember IsExpanded is usually controlled by a ToggleButton in the control theme.
- _ignorePropertyChanged = true;
-
- RaisePropertyChanged(
- IsExpandedProperty,
- oldValue: value,
- newValue: _isExpanded,
- BindingPriority.LocalValue,
- isEffectiveValue: true);
-
- _ignorePropertyChanged = false;
- }
- else
- {
- SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
- }
- }
- }
+ get => GetValue(IsExpandedProperty);
+ set => SetValue(IsExpandedProperty, value);
}
///
@@ -193,10 +149,10 @@ namespace Avalonia.Controls
/// Occurs as the content area is closing.
///
///
- /// The event args property may be set to true to cancel the event
+ /// The event args property may be set to true to cancel the event
/// and keep the control open (expanded).
///
- public event EventHandler? Collapsing
+ public event EventHandler? Collapsing
{
add => AddHandler(CollapsingEvent, value);
remove => RemoveHandler(CollapsingEvent, value);
@@ -215,10 +171,10 @@ namespace Avalonia.Controls
/// Occurs as the content area is opening.
///
///
- /// The event args property may be set to true to cancel the event
+ /// The event args property may be set to true to cancel the event
/// and keep the control closed (collapsed).
///
- public event EventHandler? Expanding
+ public event EventHandler? Expanding
{
add => AddHandler(ExpandingEvent, value);
remove => RemoveHandler(ExpandingEvent, value);
@@ -332,5 +288,63 @@ namespace Avalonia.Controls
PseudoClasses.Set(":expanded", IsExpanded);
}
+
+ ///
+ /// Called when the property has to be coerced.
+ ///
+ /// The value to coerce.
+ protected virtual bool OnCoerceIsExpanded(bool value)
+ {
+ CancelRoutedEventArgs eventArgs;
+
+ if (value)
+ {
+ eventArgs = new CancelRoutedEventArgs(ExpandingEvent, this);
+ OnExpanding(eventArgs);
+ }
+ else
+ {
+ eventArgs = new CancelRoutedEventArgs(CollapsingEvent, this);
+ OnCollapsing(eventArgs);
+ }
+
+ if (eventArgs.Cancel)
+ {
+ // If the event was externally canceled we must still notify the value has changed.
+ // This property changed notification will update any external code observing this property that itself may have set the new value.
+ // We are essentially reverted any external state change along with ignoring the IsExpanded property set.
+ // Remember IsExpanded is usually controlled by a ToggleButton in the control theme and is also used for animations.
+ _ignorePropertyChanged = true;
+
+ RaisePropertyChanged(
+ IsExpandedProperty,
+ oldValue: value,
+ newValue: !value,
+ BindingPriority.LocalValue,
+ isEffectiveValue: true);
+
+ _ignorePropertyChanged = false;
+
+ return !value;
+ }
+
+ return value;
+ }
+
+ ///
+ /// Coerces/validates the property value.
+ ///
+ /// The instance.
+ /// The value to coerce.
+ /// The coerced/validated value.
+ private static bool CoerceIsExpanded(AvaloniaObject instance, bool value)
+ {
+ if (instance is Expander expander)
+ {
+ return expander.OnCoerceIsExpanded(value);
+ }
+
+ return value;
+ }
}
}
diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyout.cs b/src/Avalonia.Controls/Flyouts/MenuFlyout.cs
index 97fda68051..b028a8f007 100644
--- a/src/Avalonia.Controls/Flyouts/MenuFlyout.cs
+++ b/src/Avalonia.Controls/Flyouts/MenuFlyout.cs
@@ -3,6 +3,7 @@ using Avalonia.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Metadata;
+using Avalonia.Styling;
namespace Avalonia.Controls
{
@@ -27,6 +28,12 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect(nameof(ItemTemplate),
x => x.ItemTemplate, (x, v) => x.ItemTemplate = v);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ItemContainerThemeProperty =
+ ItemsControl.ItemContainerThemeProperty.AddOwner();
+
public Classes FlyoutPresenterClasses => _classes ??= new Classes();
///
@@ -48,6 +55,15 @@ namespace Avalonia.Controls
set => SetAndRaise(ItemTemplateProperty, ref _itemTemplate, value);
}
+ ///
+ /// Gets or sets the that is applied to the container element generated for each item.
+ ///
+ public ControlTheme? ItemContainerTheme
+ {
+ get { return GetValue(ItemContainerThemeProperty); }
+ set { SetValue(ItemContainerThemeProperty, value); }
+ }
+
private Classes? _classes;
private IEnumerable? _items;
private IDataTemplate? _itemTemplate;
@@ -57,7 +73,8 @@ namespace Avalonia.Controls
return new MenuFlyoutPresenter
{
[!ItemsControl.ItemsProperty] = this[!ItemsProperty],
- [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty]
+ [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty],
+ [!ItemsControl.ItemContainerThemeProperty] = this[!ItemContainerThemeProperty],
};
}
diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
index 7aca21b42e..594d7da2fb 100644
--- a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
+++ b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
@@ -1,5 +1,4 @@
using System;
-using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
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/Dialogs/SystemDialogImpl.cs b/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
index 2775c53803..a8a266e378 100644
--- a/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
+++ b/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using Avalonia.Platform.Storage;
#nullable enable
@@ -26,9 +27,7 @@ namespace Avalonia.Controls.Platform
var files = await filePicker.OpenFilePickerAsync(options);
return files
- .Select(file => file.TryGetUri(out var fullPath)
- ? fullPath.LocalPath
- : file.Name)
+ .Select(file => file.TryGetFullPath() ?? file.Name)
.ToArray();
}
else if (dialog is SaveFileDialog saveDialog)
@@ -47,9 +46,7 @@ namespace Avalonia.Controls.Platform
return null;
}
- var filePath = file.TryGetUri(out var fullPath)
- ? fullPath.LocalPath
- : file.Name;
+ var filePath = file.TryGetFullPath() ?? file.Name;
return new[] { filePath };
}
return null;
@@ -67,7 +64,7 @@ namespace Avalonia.Controls.Platform
var folders = await filePicker.OpenFolderPickerAsync(options);
return folders
- .Select(f => f.TryGetUri(out var uri) ? uri.LocalPath : null)
+ .Select(folder => folder.TryGetFullPath() ?? folder.Name)
.FirstOrDefault(u => u is not 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 7d5b5e1490..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)
- {
- 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)
- {
- 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/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
index 844973bd28..4c1e0c2565 100644
--- a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
+++ b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
@@ -169,9 +169,9 @@ namespace Avalonia.Controls.PullToRefresh
visualizerComposition.ImplicitAnimations = animation;
}
- if(_scrollViewer != null && _scrollViewer.Content is Visual visual)
+ if(_scrollViewer != null)
{
- var scollContentComposition = ElementComposition.GetElementVisual(visual);
+ var scollContentComposition = ElementComposition.GetElementVisual(_scrollViewer);
if(scollContentComposition != null)
{
diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs
index 68da24d79f..b87be34a9d 100644
--- a/src/Avalonia.Controls/RadioButton.cs
+++ b/src/Avalonia.Controls/RadioButton.cs
@@ -180,7 +180,7 @@ namespace Avalonia.Controls
var siblings = parent
.GetVisualChildren()
.OfType()
- .Where(x => x != this);
+ .Where(x => x != this && string.IsNullOrEmpty(x.GroupName));
foreach (var sibling in siblings)
{
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 3bd566c622..06a829c418 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -216,9 +216,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);
@@ -382,15 +382,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)
{
@@ -622,7 +616,6 @@ 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();
}
}
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/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
index acf2e01264..103ac669b6 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
@@ -17,7 +17,11 @@ namespace Avalonia.Diagnostics.Screenshots
public async Task Take(Control control)
{
+#if NET6_0_OR_GREATER
+ await using var output = await GetStream(control);
+#else
using var output = await GetStream(control);
+#endif
if (output is { })
{
control.RenderTo(output);
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
index 2117996b96..a7d279741e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
@@ -14,57 +14,54 @@ namespace Avalonia.Diagnostics.Screenshots
///
public sealed class FilePickerHandler : BaseRenderToStreamHandler
{
+ private readonly string _title;
+ private readonly string _screenshotRoot;
+
///
/// Instance FilePickerHandler
///
- public FilePickerHandler()
+ public FilePickerHandler() : this(null, null)
{
}
+
///
/// Instance FilePickerHandler with specificated parameter
///
/// SaveFilePicker Title
///
- public FilePickerHandler(string? title
- , string? screenshotRoot = default
- )
+ public FilePickerHandler(
+ string? title,
+ string? screenshotRoot = default)
{
- if (title is { })
- Title = title;
- if (screenshotRoot is { })
- ScreenshotsRoot = screenshotRoot;
+ _title = title ?? "Save Screenshot to ...";
+ _screenshotRoot = screenshotRoot ?? Conventions.DefaultScreenshotsRoot;
}
- ///
- /// Get the root folder where screeshots well be stored.
- /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
- ///
- public string ScreenshotsRoot { get; }
- = Conventions.DefaultScreenshotsRoot;
- ///
- /// SaveFilePicker Title
- ///
- public string Title { get; } = "Save Screenshot to ...";
-
- static Window GetWindow(Control control)
+ private static TopLevel GetTopLevel(Control control)
{
- var window = control.VisualRoot as Window;
- var app = Application.Current;
- if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop)
+ // If possible, use devtools main window.
+ TopLevel? devToolsTopLevel = null;
+ if (Application.Current?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop)
{
- window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow);
+ devToolsTopLevel = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow);
}
- return window!;
+
+ return devToolsTopLevel ?? TopLevel.GetTopLevel(control)
+ ?? throw new InvalidOperationException("No TopLevel is available.");
}
protected override async Task GetStream(Control control)
{
- var result = await GetWindow(control).StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
+ var storageProvider = GetTopLevel(control).StorageProvider;
+ var defaultFolder = await storageProvider.TryGetFolderFromPath(_screenshotRoot)
+ ?? await storageProvider.TryGetWellKnownFolder(WellKnownFolder.Pictures);
+
+ var result = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
- SuggestedStartLocation = new BclStorageFolder(new DirectoryInfo(ScreenshotsRoot)),
- Title = Title,
- FileTypeChoices = new FilePickerFileType[] { new FilePickerFileType("PNG") { Patterns = new string[] { "*.png" } } },
+ SuggestedStartLocation = defaultFolder,
+ Title = _title,
+ FileTypeChoices = new [] { FilePickerFileTypes.ImagePng },
DefaultExtension = ".png"
});
if (result is null)
@@ -73,7 +70,7 @@ namespace Avalonia.Diagnostics.Screenshots
}
if (!result.CanOpenWrite)
{
- throw new InvalidOperationException("ReadOnly file was opened");
+ throw new InvalidOperationException("Read-only file was selected.");
}
return await result.OpenWriteAsync();
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/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs
index 092e26c96d..ff11fc357e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs
@@ -17,11 +17,11 @@ namespace Avalonia.Diagnostics
/// Dpi quality.
public static void RenderTo(this Control source, Stream destination, double dpi = 96)
{
- if (source.TransformedBounds == null)
- {
+ var transform = source.CompositionVisual?.TryGetServerGlobalTransform();
+ if (transform == null)
return;
- }
- var rect = source.TransformedBounds.Value.Clip;
+
+ var rect = new Rect(source.Bounds.Size).TransformToAABB(transform.Value);
var top = rect.TopLeft;
var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height);
var dpiVector = new Vector(dpi, dpi);
diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
index 99cf1538ae..be4bae26f1 100644
--- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
+++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
@@ -14,6 +14,11 @@
+
+
+
+
+
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
index df67c06b73..46de460b1a 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
@@ -260,17 +260,7 @@ namespace Avalonia.Dialogs.Internal
public void Navigate(IStorageFolder path, string initialSelectionName = null)
{
- string fullDirectoryPath;
-
- if (path?.TryGetUri(out var fullDirectoryUri) == true
- && fullDirectoryUri.IsAbsoluteUri)
- {
- fullDirectoryPath = fullDirectoryUri.LocalPath;
- }
- else
- {
- fullDirectoryPath = Directory.GetCurrentDirectory();
- }
+ var fullDirectoryPath = path?.TryGetFullPath() ?? Directory.GetCurrentDirectory();
Navigate(fullDirectoryPath, initialSelectionName);
}
diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
index bc5945441c..cad938ac35 100644
--- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
+++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
@@ -51,9 +51,7 @@ namespace Avalonia.Dialogs
var files = await impl.OpenFilePickerAsync(dialog.ToFilePickerOpenOptions());
return files
- .Select(file => file.TryGetUri(out var fullPath)
- ? fullPath.LocalPath
- : file.Name)
+ .Select(file => file.TryGetFullPath() ?? file.Name)
.ToArray();
}
}
diff --git a/src/Avalonia.Dialogs/ManagedStorageProvider.cs b/src/Avalonia.Dialogs/ManagedStorageProvider.cs
index e45d5c1b4a..3e7c58c689 100644
--- a/src/Avalonia.Dialogs/ManagedStorageProvider.cs
+++ b/src/Avalonia.Dialogs/ManagedStorageProvider.cs
@@ -11,7 +11,7 @@ using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.Dialogs;
-public class ManagedStorageProvider : BclStorageProvider where T : Window, new()
+internal class ManagedStorageProvider : BclStorageProvider where T : Window, new()
{
private readonly Window _parent;
private readonly ManagedFileDialogOptions _managedOptions;
diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
index 899288b4a8..7d97c7cd36 100644
--- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
+++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
@@ -34,7 +34,7 @@ namespace Avalonia.FreeDesktop.DBusIme
private bool _connecting;
private string? _currentName;
private DBusCallQueue _queue;
- private bool _controlActive, _windowActive;
+ private bool _windowActive;
private bool? _imeActive;
private Rect _logicalRect;
private PixelRect? _lastReportedRect;
@@ -189,7 +189,7 @@ namespace Avalonia.FreeDesktop.DBusIme
if(!IsConnected)
return;
- var active = _windowActive && _controlActive;
+ var active = _windowActive && IsActive;
if (active != _imeActive)
{
_imeActive = active;
diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
index 8597f3922a..6f647215be 100644
--- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
+++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
@@ -88,8 +88,8 @@ namespace Avalonia.FreeDesktop
if (options.SuggestedFileName is { } currentName)
chooserOptions.Add("current_name", currentName);
- if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true)
- chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString()));
+ if (options.SuggestedStartLocation?.TryGetFullPath() is { } folderPath)
+ chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(folderPath));
objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = DBusHelper.Connection!.CreateProxy("org.freedesktop.portal.Request", objectPath);
@@ -131,7 +131,7 @@ namespace Avalonia.FreeDesktop
.Where(Directory.Exists)
.Select(path => new BclStorageFolder(new DirectoryInfo(path))).ToList();
}
-
+
private static (string name, (uint style, string extension)[])[] ParseFilters(IReadOnlyList? fileTypes)
{
// Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]
diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
index b6bb69b05d..efc8c66fde 100644
--- a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
+++ b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
@@ -21,7 +21,6 @@ namespace Avalonia
return builder
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
- UseCompositor = true,
UseHeadlessDrawing = false
})
.AfterSetup(_ =>
diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
index 301a23b608..9834969898 100644
--- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
+++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
@@ -72,8 +72,7 @@ namespace Avalonia.Headless
.Bind().ToSingleton()
.Bind().ToConstant(new HeadlessWindowingPlatform())
.Bind().ToSingleton();
- if (opts.UseCompositor)
- Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null);
+ Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null);
}
@@ -88,7 +87,6 @@ namespace Avalonia.Headless
public class AvaloniaHeadlessPlatformOptions
{
- public bool UseCompositor { get; set; } = true;
public bool UseHeadlessDrawing { get; set; } = true;
}
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 514d3b3e07..225e846390 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -30,6 +30,7 @@ namespace Avalonia.Headless
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect);
@@ -353,6 +354,8 @@ namespace Avalonia.Headless
}
+ public PixelFormat? Format { get; }
+
public ILockedFramebuffer Lock()
{
Version++;
diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs
index 195328cd65..15e2c696ac 100644
--- a/src/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -18,7 +18,7 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
- class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow, ITopLevelImplWithStorageProvider
+ class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
{
private IKeyboardDevice _keyboard;
private Stopwatch _st = Stopwatch.StartNew();
@@ -54,11 +54,8 @@ namespace Avalonia.Headless
public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
- public IRenderer CreateRenderer(IRenderRoot root)
- => AvaloniaHeadlessPlatform.Compositor != null
- ? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces)
- : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(),
- () => new PlatformRenderInterfaceContextManager(null).CreateRenderTarget(Surfaces), null);
+ public IRenderer CreateRenderer(IRenderRoot root) =>
+ new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces);
public void Invalidate(Rect rect)
{
@@ -250,8 +247,15 @@ namespace Avalonia.Headless
public Action LostFocus { get; set; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
+ public object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(IStorageProvider))
+ {
+ return new NoopStorageProvider();
+ }
- public IStorageProvider StorageProvider => new NoopStorageProvider();
+ return null;
+ }
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers)
{
@@ -284,6 +288,18 @@ namespace Avalonia.Headless
button == 1 ? RawPointerEventType.MiddleButtonUp : RawPointerEventType.RightButtonUp,
point, modifiers));
}
+
+ void IHeadlessWindow.MouseWheel(Point point, Vector delta, RawInputModifiers modifiers)
+ {
+ Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, InputRoot,
+ point, delta, modifiers));
+ }
+
+ void IHeadlessWindow.DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
+ {
+ var device = AvaloniaLocator.Current.GetRequiredService();
+ Input?.Invoke(new RawDragEvent(device, type, InputRoot, point, data, effects, modifiers));
+ }
void IWindowImpl.Move(PixelPoint point)
{
diff --git a/src/Avalonia.Headless/IHeadlessWindow.cs b/src/Avalonia.Headless/IHeadlessWindow.cs
index 282662f98b..dfb3a4c433 100644
--- a/src/Avalonia.Headless/IHeadlessWindow.cs
+++ b/src/Avalonia.Headless/IHeadlessWindow.cs
@@ -1,4 +1,5 @@
using Avalonia.Input;
+using Avalonia.Input.Raw;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Utilities;
@@ -13,5 +14,7 @@ namespace Avalonia.Headless
void MouseDown(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseUp(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
+ void MouseWheel(Point point, Vector delta, RawInputModifiers modifiers = RawInputModifiers.None);
+ void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers);
}
}
diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj
index 93fba4a57c..095662a538 100644
--- a/src/Avalonia.Native/Avalonia.Native.csproj
+++ b/src/Avalonia.Native/Avalonia.Native.csproj
@@ -16,10 +16,6 @@
PreserveNewest
-
-
-
-
@@ -30,4 +26,8 @@
+
+
+
+
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index fc23fbb226..09feb0c768 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -24,8 +24,7 @@ namespace Avalonia.Native
static extern IntPtr CreateAvaloniaNative();
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
- internal static Compositor? Compositor { get; private set; }
- internal static PlatformRenderInterfaceContextManager? RenderInterface { get; private set; }
+ internal static Compositor Compositor { get; private set; } = null!;
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
@@ -140,14 +139,8 @@ namespace Avalonia.Native
// ignored
}
}
-
-
- if (_options.UseDeferredRendering && _options.UseCompositor)
- {
- Compositor = new Compositor(renderLoop, _platformGl);
- }
- else
- RenderInterface = new PlatformRenderInterfaceContextManager(_platformGl);
+
+ Compositor = new Compositor(renderLoop, _platformGl);
}
public ITrayIconImpl CreateTrayIcon()
diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
index 6613fc09be..2b989ce733 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
@@ -30,20 +30,6 @@ namespace Avalonia
///
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/SystemDialogs.cs b/src/Avalonia.Native/SystemDialogs.cs
index 49ba652b1a..60724507f3 100644
--- a/src/Avalonia.Native/SystemDialogs.cs
+++ b/src/Avalonia.Native/SystemDialogs.cs
@@ -33,8 +33,7 @@ namespace Avalonia.Native
{
using var events = new SystemDialogEvents();
- var suggestedDirectory = options.SuggestedStartLocation?.TryGetUri(out var suggestedDirectoryTmp) == true
- ? suggestedDirectoryTmp.LocalPath : string.Empty;
+ var suggestedDirectory = options.SuggestedStartLocation?.TryGetFullPath() ?? string.Empty;
_native.OpenFileDialog((IAvnWindow)_window.Native,
events,
@@ -54,8 +53,7 @@ namespace Avalonia.Native
{
using var events = new SystemDialogEvents();
- var suggestedDirectory = options.SuggestedStartLocation?.TryGetUri(out var suggestedDirectoryTmp) == true
- ? suggestedDirectoryTmp.LocalPath : string.Empty;
+ var suggestedDirectory = options.SuggestedStartLocation?.TryGetFullPath() ?? string.Empty;
_native.SaveFileDialog((IAvnWindow)_window.Native,
events,
@@ -74,8 +72,7 @@ namespace Avalonia.Native
{
using var events = new SystemDialogEvents();
- var suggestedDirectory = options.SuggestedStartLocation?.TryGetUri(out var suggestedDirectoryTmp) == true
- ? suggestedDirectoryTmp.LocalPath : string.Empty;
+ var suggestedDirectory = options.SuggestedStartLocation?.TryGetFullPath() ?? string.Empty;
_native.SelectFolderDialog((IAvnWindow)_window.Native, events, options.AllowMultiple.AsComBool(), options.Title ?? "", suggestedDirectory);
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/NativeDialogs/CompositeStorageProvider.cs b/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs
index 5fe0f46b14..07a11ff2ec 100644
--- a/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs
+++ b/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs
@@ -61,4 +61,22 @@ internal class CompositeStorageProvider : IStorageProvider
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFolderBookmarkAsync(bookmark).ConfigureAwait(false);
}
+
+ public async Task TryGetFileFromPath(Uri filePath)
+ {
+ var provider = await EnsureStorageProvider().ConfigureAwait(false);
+ return await provider.TryGetFileFromPath(filePath).ConfigureAwait(false);
+ }
+
+ public async Task TryGetFolderFromPath(Uri folderPath)
+ {
+ var provider = await EnsureStorageProvider().ConfigureAwait(false);
+ return await provider.TryGetFolderFromPath(folderPath).ConfigureAwait(false);
+ }
+
+ public async Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
+ {
+ var provider = await EnsureStorageProvider().ConfigureAwait(false);
+ return await provider.TryGetWellKnownFolder(wellKnownFolder).ConfigureAwait(false);
+ }
}
diff --git a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
index 89aa0340b5..ceb32e52e9 100644
--- a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
+++ b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
@@ -59,7 +59,7 @@ namespace Avalonia.X11.NativeDialogs
return res?.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray() ?? Array.Empty();
});
}
-
+
public override async Task SaveFilePickerAsync(FilePickerSaveOptions options)
{
return await await RunOnGlibThread(async () =>
@@ -196,10 +196,10 @@ namespace Avalonia.X11.NativeDialogs
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
}
- Uri? folderPath = null;
- if (initialFolder?.TryGetUri(out folderPath) == true)
+ var folderLocalPath = initialFolder?.TryGetFullPath();
+ if (folderLocalPath is not null)
{
- using var dir = new Utf8Buffer(folderPath.LocalPath);
+ using var dir = new Utf8Buffer(folderLocalPath);
gtk_file_chooser_set_current_folder(dlg, dir);
}
@@ -207,7 +207,7 @@ namespace Avalonia.X11.NativeDialogs
{
// gtk_file_chooser_set_filename() expects full path
using var fn = action == GtkFileChooserAction.Open
- ? new Utf8Buffer(Path.Combine(folderPath?.LocalPath ?? "", initialFileName))
+ ? new Utf8Buffer(Path.Combine(folderLocalPath ?? "", initialFileName))
: new Utf8Buffer(initialFileName);
if (action == GtkFileChooserAction.Save)
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.Xim.cs b/src/Avalonia.X11/X11Window.Xim.cs
index 10aece7f2e..8446d35cc6 100644
--- a/src/Avalonia.X11/X11Window.Xim.cs
+++ b/src/Avalonia.X11/X11Window.Xim.cs
@@ -13,7 +13,7 @@ namespace Avalonia.X11
class XimInputMethod : ITextInputMethodImpl, IX11InputMethodControl
{
private readonly X11Window _parent;
- private bool _controlActive, _windowActive, _imeActive;
+ private bool _windowActive, _imeActive;
private Rect? _queuedCursorRect;
private ITextInputMethodClient? _client;
@@ -70,7 +70,7 @@ namespace Avalonia.X11
private void UpdateActive()
{
- var active = _windowActive && _controlActive;
+ var active = _windowActive && IsActive;
if(_parent._xic == IntPtr.Zero)
return;
if (active != _imeActive)
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/Browser/Avalonia.Browser/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs
index e50f8790ef..a4d7bcdb87 100644
--- a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs
+++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs
@@ -25,6 +25,9 @@ internal static partial class StorageHelper
public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName,
[JSMarshalAs>] object[]? types, bool excludeAcceptAllOption);
+ [JSImport("StorageItem.createWellKnownDirectory", AvaloniaModule.StorageModuleName)]
+ public static partial JSObject CreateWellKnownDirectory(string wellKnownDirectory);
+
[JSImport("StorageProvider.openBookmark", AvaloniaModule.StorageModuleName)]
public static partial Task OpenBookmark(string key);
diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs
index 0bebcb9f63..7e8e2e0990 100644
--- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs
+++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs
@@ -116,6 +116,33 @@ internal class BrowserStorageProvider : IStorageProvider
return item is not null ? new JSStorageFolder(item) : null;
}
+ public Task TryGetFileFromPath(Uri filePath)
+ {
+ return Task.FromResult(null);
+ }
+
+ public Task TryGetFolderFromPath(Uri folderPath)
+ {
+ return Task.FromResult(null);
+ }
+
+ public async Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
+ {
+ await _lazyModule.Value;
+ var directory = StorageHelper.CreateWellKnownDirectory(wellKnownFolder switch
+ {
+ WellKnownFolder.Desktop => "desktop",
+ WellKnownFolder.Documents => "documents",
+ WellKnownFolder.Downloads => "downloads",
+ WellKnownFolder.Music => "music",
+ WellKnownFolder.Pictures => "pictures",
+ WellKnownFolder.Videos => "videos",
+ _ => throw new ArgumentOutOfRangeException(nameof(wellKnownFolder), wellKnownFolder, null)
+ });
+
+ return new JSStorageFolder(directory);
+ }
+
private static (JSObject[]? types, bool excludeAllOption) ConvertFileTypes(IEnumerable? input)
{
var types = input?
@@ -145,12 +172,7 @@ internal abstract class JSStorageItem : IStorageBookmarkItem
internal JSObject FileHandle => _fileHandle ?? throw new ObjectDisposedException(nameof(JSStorageItem));
public string Name => FileHandle.GetPropertyAsString("name") ?? string.Empty;
-
- public bool TryGetUri([NotNullWhen(true)] out Uri? uri)
- {
- uri = new Uri(Name, UriKind.Relative);
- return false;
- }
+ public Uri Path => new Uri(Name, UriKind.Relative);
public async Task GetBasicPropertiesAsync()
{
diff --git a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts
index b4cf3d278b..c6e5254329 100644
--- a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts
+++ b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts
@@ -1,14 +1,29 @@
import { avaloniaDb, fileBookmarksStore } from "./indexedDb";
export class StorageItem {
- constructor(public handle: FileSystemHandle, private readonly bookmarkId?: string) { }
+ constructor(
+ public handle?: FileSystemHandle,
+ private readonly bookmarkId?: string,
+ public wellKnownType?: WellKnownDirectory
+ ) {
+ }
public get name(): string {
- return this.handle.name;
+ if (this.handle) {
+ return this.handle.name;
+ }
+ return this.wellKnownType ?? "";
+ }
+
+ public get kind(): "file" | "directory" {
+ if (this.handle) {
+ return this.handle.kind;
+ }
+ return "directory";
}
- public get kind(): string {
- return this.handle.kind;
+ public static createWellKnownDirectory(type: WellKnownDirectory) {
+ return new StorageItem(undefined, undefined, type);
}
public static async openRead(item: StorageItem): Promise {
@@ -48,7 +63,7 @@ export class StorageItem {
}
public static async getItems(item: StorageItem): Promise {
- if (item.handle.kind !== "directory") {
+ if (item.kind !== "directory" || !item.handle) {
return new StorageItems([]);
}
@@ -60,6 +75,10 @@ export class StorageItem {
}
private async verityPermissions(mode: FileSystemPermissionMode): Promise {
+ if (!this.handle) {
+ return;
+ }
+
if (await this.handle.queryPermission({ mode }) === "granted") {
return;
}
@@ -69,11 +88,14 @@ export class StorageItem {
}
}
- public static async saveBookmark(item: StorageItem): Promise {
+ public static async saveBookmark(item: StorageItem): Promise {
// If file was previously bookmarked, just return old one.
if (item.bookmarkId) {
return item.bookmarkId;
}
+ if (!item.handle) {
+ return null;
+ }
const connection = await avaloniaDb.connect();
try {
diff --git a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts
index 0198f92528..e621a1ed30 100644
--- a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts
+++ b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts
@@ -17,7 +17,7 @@ export class StorageProvider {
startIn: StorageItem | null): Promise {
// 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined.
const options: DirectoryPickerOptions = {
- startIn: (startIn?.handle ?? undefined)
+ startIn: (startIn?.wellKnownType ?? startIn?.handle ?? undefined)
};
const handle = await window.showDirectoryPicker(options);
@@ -28,7 +28,7 @@ export class StorageProvider {
startIn: StorageItem | null, multiple: boolean,
types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean): Promise {
const options: OpenFilePickerOptions = {
- startIn: (startIn?.handle ?? undefined),
+ startIn: (startIn?.wellKnownType ?? startIn?.handle ?? undefined),
multiple,
excludeAcceptAllOption,
types: (types ?? undefined)
@@ -42,7 +42,7 @@ export class StorageProvider {
startIn: StorageItem | null, suggestedName: string | null,
types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean): Promise {
const options: SaveFilePickerOptions = {
- startIn: (startIn?.handle ?? undefined),
+ startIn: (startIn?.wellKnownType ?? startIn?.handle ?? undefined),
suggestedName: (suggestedName ?? undefined),
excludeAcceptAllOption,
types: (types ?? undefined)
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 0b61316603..0aa3dda693 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; }
@@ -135,6 +136,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/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
index 4270063f87..ba321db144 100644
--- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
+++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
@@ -137,9 +137,9 @@ namespace Avalonia.Data
templatedParent.GetValue(Property) :
_target.TemplatedParent;
- if (Converter is not null && _targetType is not null)
+ if (Converter is not null)
{
- value = Converter.Convert(value, _targetType, ConverterParameter, CultureInfo.CurrentCulture);
+ value = Converter.Convert(value, _targetType ?? typeof(object), ConverterParameter, CultureInfo.CurrentCulture);
}
PublishNext(value);
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/Win32StorageProvider.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs
index 8cc0a380f9..2da642bd19 100644
--- a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs
+++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs
@@ -134,10 +134,10 @@ namespace Avalonia.Win32
}
}
- if (folder?.TryGetUri(out var folderPath) == true)
+ if (folder?.TryGetFullPath() is { } folderPath)
{
var riid = UnmanagedMethods.ShellIds.IShellItem;
- if (UnmanagedMethods.SHCreateItemFromParsingName(folderPath.LocalPath, IntPtr.Zero, ref riid, out var directoryShellItem)
+ if (UnmanagedMethods.SHCreateItemFromParsingName(folderPath, IntPtr.Zero, ref riid, out var directoryShellItem)
== (uint)UnmanagedMethods.HRESULT.S_OK)
{
var proxy = MicroComRuntime.CreateProxyFor(directoryShellItem, true);
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/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
index a801e83562..ef0e2467dc 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
@@ -25,7 +25,7 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
using (var doc = new UIDocument(url))
{
_filePath = doc.FileUrl?.Path ?? url.FilePathUrl.Path;
- Name = doc.LocalizedName ?? Path.GetFileName(_filePath) ?? url.FilePathUrl.LastPathComponent;
+ Name = doc.LocalizedName ?? System.IO.Path.GetFileName(_filePath) ?? url.FilePathUrl.LastPathComponent;
}
}
@@ -34,6 +34,7 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
public bool CanBookmark => true;
public string Name { get; }
+ public Uri Path => Url!;
public Task GetBasicPropertiesAsync()
{
@@ -83,12 +84,6 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
}
}
- public bool TryGetUri([NotNullWhen(true)] out Uri uri)
- {
- uri = Url;
- return uri is not null;
- }
-
public void Dispose()
{
}
@@ -121,18 +116,34 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
{
}
- public Task> GetItemsAsync()
+ public async Task> GetItemsAsync()
{
- var content = NSFileManager.DefaultManager.GetDirectoryContent(Url, null, NSDirectoryEnumerationOptions.None, out var error);
+ var tcs = new TaskCompletionSource>();
+
+ new NSFileCoordinator().CoordinateRead(Url,
+ NSFileCoordinatorReadingOptions.WithoutChanges,
+ out var error,
+ uri =>
+ {
+ var content = NSFileManager.DefaultManager.GetDirectoryContent(uri, null, NSDirectoryEnumerationOptions.None, out var error);
+ if (error is not null)
+ {
+ tcs.TrySetException(new NSErrorException(error));
+ }
+ else
+ {
+ var items = content
+ .Select(u => u.HasDirectoryPath ? (IStorageItem)new IOSStorageFolder(u) : new IOSStorageFile(u))
+ .ToArray();
+ tcs.TrySetResult(items);
+ }
+ });
+
if (error is not null)
{
- return Task.FromException>(new NSErrorException(error));
+ throw new NSErrorException(error);
}
- var items = content
- .Select(u => u.HasDirectoryPath ? (IStorageItem)new IOSStorageFolder(u) : new IOSStorageFile(u))
- .ToArray();
-
- return Task.FromResult>(items);
+ return await tcs.Task;
}
}
diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
index 5fdb7ecce1..376e988e74 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
+using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Platform.Storage;
using UIKit;
@@ -99,6 +100,40 @@ internal class IOSStorageProvider : IStorageProvider
? new IOSStorageFolder(url) : null);
}
+ public Task TryGetFileFromPath(Uri filePath)
+ {
+ // TODO: research if it's possible, maybe with additional permissions.
+ return Task.FromResult(null);
+ }
+
+ public Task TryGetFolderFromPath(Uri folderPath)
+ {
+ // TODO: research if it's possible, maybe with additional permissions.
+ return Task.FromResult(null);
+ }
+
+ public Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
+ {
+ var directoryType = wellKnownFolder switch
+ {
+ WellKnownFolder.Desktop => NSSearchPathDirectory.DesktopDirectory,
+ WellKnownFolder.Documents => NSSearchPathDirectory.DocumentDirectory,
+ WellKnownFolder.Downloads => NSSearchPathDirectory.DownloadsDirectory,
+ WellKnownFolder.Music => NSSearchPathDirectory.MusicDirectory,
+ WellKnownFolder.Pictures => NSSearchPathDirectory.PicturesDirectory,
+ WellKnownFolder.Videos => NSSearchPathDirectory.MoviesDirectory,
+ _ => throw new ArgumentOutOfRangeException(nameof(wellKnownFolder), wellKnownFolder, null)
+ };
+
+ var uri = NSFileManager.DefaultManager.GetUrl(directoryType, NSSearchPathDomain.Local, null, true, out var error);
+ if (error != null)
+ {
+ throw new NSErrorException(error);
+ }
+
+ return Task.FromResult(new IOSStorageFolder(uri));
+ }
+
public Task SaveFilePickerAsync(FilePickerSaveOptions options)
{
return Task.FromException(
@@ -127,17 +162,12 @@ internal class IOSStorageProvider : IStorageProvider
private static NSUrl? GetUrlFromFolder(IStorageFolder? folder)
{
- if (folder is IOSStorageFolder iosFolder)
+ return folder switch
{
- return iosFolder.Url;
- }
-
- if (folder?.TryGetUri(out var fullPath) == true)
- {
- return fullPath;
- }
-
- return null;
+ IOSStorageFolder iosFolder => iosFolder.Url,
+ null => null,
+ _ => folder.Path
+ };
}
private Task ShowPicker(UIDocumentPickerViewController documentPicker)
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/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/Utilities/UriExtensionsTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs
index 4a879c8ced..b648b0d178 100644
--- a/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Platform.Storage.FileIO;
using Avalonia.Utilities;
using Xunit;
@@ -26,4 +27,17 @@ public class UriExtensionsTests
Assert.Equal(string.Empty, name);
}
+
+ [Theory]
+ [InlineData("/home/Projects.txt")]
+ [InlineData("/home/Stahování/Požární kniha 2.txt")]
+ [InlineData("C:\\%51.txt")]
+ [InlineData("/home/asd#xcv.txt")]
+ [InlineData("C:\\\\Work\\Projects.txt")]
+ public void Should_Convert_File_Path_To_Uri_And_Back(string path)
+ {
+ var uri = StorageProviderHelpers.FilePathToUri(path);
+
+ Assert.Equal(path, uri.LocalPath);
+ }
}
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/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/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
index b358c98a62..942eb8bf5b 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
@@ -28,7 +28,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Shift
});
@@ -52,7 +51,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
@@ -77,7 +75,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
index cb4e81001f..131642fc4e 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
@@ -33,7 +33,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Tab,
});
@@ -53,7 +52,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
});
@@ -73,7 +71,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
@@ -96,7 +93,6 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
- RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
diff --git a/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs b/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs
index 7c5249b2c4..d5e6f4885e 100644
--- a/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs
@@ -70,5 +70,44 @@ namespace Avalonia.Controls.UnitTests
Assert.False(radioButton2.IsChecked);
Assert.True(radioButton3.IsChecked);
}
+
+ [Fact]
+ public void RadioButton_Empty_GroupName_Not_Influence_Other_Groups()
+ {
+ var parent = new Panel();
+
+ var radioButton1 = new RadioButton();
+ radioButton1.GroupName = "A";
+ radioButton1.IsChecked = true;
+ var radioButton2 = new RadioButton();
+ radioButton2.GroupName = "A";
+ radioButton2.IsChecked = false;
+
+ var radioButton3 = new RadioButton();
+ radioButton3.GroupName = null;
+ radioButton3.IsChecked = false;
+ var radioButton4 = new RadioButton();
+ radioButton4.GroupName = null;
+ radioButton4.IsChecked = true;
+
+ parent.Children.Add(radioButton1);
+ parent.Children.Add(radioButton2);
+ parent.Children.Add(radioButton3);
+ parent.Children.Add(radioButton4);
+
+ Assert.True(radioButton1.IsChecked);
+ Assert.False(radioButton2.IsChecked);
+ Assert.False(radioButton3.IsChecked);
+ Assert.True(radioButton4.IsChecked);
+
+ radioButton3.IsChecked = true;
+
+ Assert.True(radioButton1.IsChecked);
+ Assert.False(radioButton2.IsChecked);
+ Assert.True(radioButton3.IsChecked);
+ Assert.False(radioButton4.IsChecked);
+
+
+ }
}
}
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..678fb5c163 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -465,6 +465,7 @@ namespace Avalonia.LeakTests
var renderer = new Mock();
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);
diff --git a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs
index 979dbec674..1e56981b98 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs
@@ -248,6 +248,38 @@ namespace Avalonia.Markup.UnitTests.Data
// binding is initiated.
Assert.Equal(new[] { "foo" }, converter.Values);
}
+
+ [Fact]
+ public void Should_Execute_Converter_Without_Specific_TargetType()
+ {
+ // See https://github.com/AvaloniaUI/Avalonia/issues/9766
+ var source = new Button
+ {
+ Template = new FuncControlTemplate((parent, _) =>
+ new ContentPresenter
+ {
+ [~ContentPresenter.IsVisibleProperty] = new MultiBinding
+ {
+ Converter = BoolConverters.And,
+ Bindings =
+ {
+ new TemplateBinding(ContentControl.ContentProperty)
+ {
+ Converter = ObjectConverters.IsNotNull
+ }
+ }
+ }
+ }),
+ };
+
+ source.ApplyTemplate();
+
+ var target = (ContentPresenter)source.GetVisualChildren().Single();
+
+ Assert.False(target.IsVisible);
+ source.Content = "foo";
+ Assert.True(target.IsVisible);
+ }
private class PrefixConverter : IValueConverter
{
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..7f28477d09 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -35,6 +35,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;
@@ -95,6 +97,7 @@ namespace Avalonia.UnitTests
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/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().ToConstant(Services.StandardCursorFactory)
.Bind().ToConstant(Services.WindowingPlatform)
.Bind().ToSingleton();
+
+ // This is a hack to make tests work, we need to refactor the way font manager is registered
+ // See https://github.com/AvaloniaUI/Avalonia/issues/10081
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant((FontManager)null!);
var theme = Services.Theme?.Invoke();
if (theme is Style styles)
diff --git a/tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png b/tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png
deleted file mode 100644
index 825b1d7ea7..0000000000
Binary files a/tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png and /dev/null differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits
new file mode 100644
index 0000000000..7b74651405
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr24.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr24.bits
new file mode 100644
index 0000000000..3c6b96c687
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr24.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr32.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr32.bits
new file mode 100644
index 0000000000..db58088c15
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr555.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr555.bits
new file mode 100644
index 0000000000..144529b050
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr555.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png b/tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png
new file mode 100644
index 0000000000..e580fe77ee
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr565.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr565.bits
new file mode 100644
index 0000000000..ba29ff03b8
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr565.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png b/tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png
new file mode 100644
index 0000000000..c5fdc072cb
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgra32.bits b/tests/TestFiles/PixelFormats/Lenna/Bgra32.bits
new file mode 100644
index 0000000000..db58088c15
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgra32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits
new file mode 100644
index 0000000000..583dc86cfe
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png
new file mode 100644
index 0000000000..0a352d448c
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits b/tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits
new file mode 100644
index 0000000000..a10913bb6b
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Default.expected.png b/tests/TestFiles/PixelFormats/Lenna/Default.expected.png
new file mode 100644
index 0000000000..df9a62ee1d
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Default.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray16.bits b/tests/TestFiles/PixelFormats/Lenna/Gray16.bits
new file mode 100644
index 0000000000..6cfab5c376
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray16.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png
new file mode 100644
index 0000000000..50048304da
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray2.bits b/tests/TestFiles/PixelFormats/Lenna/Gray2.bits
new file mode 100644
index 0000000000..adb818a1df
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray2.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png
new file mode 100644
index 0000000000..b7f3d892f4
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits
new file mode 100644
index 0000000000..34ba7a94b5
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png
new file mode 100644
index 0000000000..e997d30d79
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray4.bits b/tests/TestFiles/PixelFormats/Lenna/Gray4.bits
new file mode 100644
index 0000000000..b24466315b
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray4.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png
new file mode 100644
index 0000000000..d8d4fb0afa
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray8.bits b/tests/TestFiles/PixelFormats/Lenna/Gray8.bits
new file mode 100644
index 0000000000..46708a66fa
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray8.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png
new file mode 100644
index 0000000000..ab406499bb
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits b/tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits
new file mode 100644
index 0000000000..db58088c15
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits b/tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits
new file mode 100644
index 0000000000..1a000186f1
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Prgba64.bits b/tests/TestFiles/PixelFormats/Lenna/Prgba64.bits
new file mode 100644
index 0000000000..1012f290fc
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Prgba64.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits b/tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits
new file mode 100644
index 0000000000..1a000186f1
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgb24.bits b/tests/TestFiles/PixelFormats/Lenna/Rgb24.bits
new file mode 100644
index 0000000000..6ade0dbad2
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgb24.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgb48.bits b/tests/TestFiles/PixelFormats/Lenna/Rgb48.bits
new file mode 100644
index 0000000000..91843b0891
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgb48.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits b/tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits
new file mode 100644
index 0000000000..1a000186f1
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgba64.bits b/tests/TestFiles/PixelFormats/Lenna/Rgba64.bits
new file mode 100644
index 0000000000..1012f290fc
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgba64.bits differ
diff --git a/tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png b/tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png
deleted file mode 100644
index f7cc90678a..0000000000
Binary files a/tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png and /dev/null differ