247 changed files with 7596 additions and 4007 deletions
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>False</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,40 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)</Target> |
|||
<Left>baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Left> |
|||
<Right>current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyPress(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target> |
|||
<Left>baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Left> |
|||
<Right>current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyRelease(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target> |
|||
<Left>baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Left> |
|||
<Right>current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)</Target> |
|||
<Left>baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Left> |
|||
<Right>current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyPress(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target> |
|||
<Left>baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Left> |
|||
<Right>current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyRelease(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target> |
|||
<Left>baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Left> |
|||
<Right>current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
File diff suppressed because it is too large
@ -0,0 +1,45 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.BitmapCachePage"> |
|||
<DockPanel> |
|||
<DockPanel.Resources> |
|||
<BitmapCache x:Key="Cache"/> |
|||
<ScaleTransform x:Key="Transform" ScaleX="1" ScaleY="{Binding $self.ScaleX, Mode=OneWay}"/> |
|||
<TranslateTransform x:Key="SubPixelTransform"/> |
|||
</DockPanel.Resources> |
|||
<StackPanel DockPanel.Dock="Right" ZIndex="1"> |
|||
<TextBlock>Render at scale</TextBlock> |
|||
<Slider Minimum="0.1" Maximum="4" Value="{Binding Source={StaticResource Cache}, Path=RenderAtScale, Mode=TwoWay}" Width="200"/> |
|||
<TextBlock>Scale</TextBlock> |
|||
<Slider Minimum="0.1" Maximum="4" Value="{Binding Source={StaticResource Transform}, Path=ScaleX, Mode=TwoWay}" Width="200"/> |
|||
<CheckBox IsChecked="{Binding Source={StaticResource Cache}, Path=EnableClearType, Mode=TwoWay}">Enable clear type</CheckBox> |
|||
|
|||
<CheckBox IsChecked="{Binding Source={StaticResource Cache}, Path=SnapsToDevicePixels, Mode=TwoWay}">Snap to device pixels</CheckBox> |
|||
<TextBlock>Subpixel offset X</TextBlock> |
|||
<Slider Minimum="0" Maximum="1" Value="{Binding Source={StaticResource SubPixelTransform}, Path=X, Mode=TwoWay}" Width="200"/> |
|||
</StackPanel> |
|||
<Decorator RenderTransform="{StaticResource SubPixelTransform}"> |
|||
<Border Background="Beige" Margin="10" |
|||
RenderTransform="{StaticResource Transform}" |
|||
RenderTransformOrigin="0.5,0.5"> |
|||
<Border Margin="10" |
|||
CacheMode="{StaticResource Cache}" |
|||
TextElement.Foreground="Black"> |
|||
<Border |
|||
Margin="10" |
|||
BorderThickness="2" |
|||
BorderBrush="Black" |
|||
Background="White"> |
|||
<StackPanel> |
|||
<Slider MinWidth="200"/> |
|||
<TextBlock TextWrapping="Wrap">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</TextBlock> |
|||
</StackPanel> |
|||
</Border> |
|||
</Border> |
|||
</Border> |
|||
</Decorator> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,13 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages; |
|||
|
|||
public partial class BitmapCachePage : UserControl |
|||
{ |
|||
public BitmapCachePage() |
|||
{ |
|||
InitializeComponent(); |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
using System; |
|||
using Android.Views; |
|||
|
|||
namespace Avalonia.Android.Platform.Specific |
|||
{ |
|||
public interface IAndroidView |
|||
{ |
|||
[Obsolete("Use TopLevel.TryGetPlatformHandle instead, which can be casted to AndroidViewControlHandle.")] |
|||
View View { get; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings; |
|||
|
|||
[Obsolete("Use SplineEasing instead")] |
|||
public sealed class CubicBezierEasing : IEasing |
|||
{ |
|||
private CubicBezierEasing() |
|||
{ |
|||
} |
|||
|
|||
public Point ControlPoint2 { get; set; } |
|||
public Point ControlPoint1 { get; set; } |
|||
|
|||
double IEasing.Ease(double progress) |
|||
=> throw new NotSupportedException(); |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
|
|||
namespace Avalonia.Data.Core.Parsers; |
|||
|
|||
/// <summary>
|
|||
/// Stores reflection members used by <see cref="BindingExpressionVisitor{TIn}"/> outside of the
|
|||
/// generic class to avoid duplication for each generic instantiation.
|
|||
/// </summary>
|
|||
internal static class BindingExpressionVisitorMembers |
|||
{ |
|||
static BindingExpressionVisitorMembers() |
|||
{ |
|||
AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty(CommonPropertyNames.IndexerName, [typeof(AvaloniaProperty)])!; |
|||
CreateDelegateMethod = typeof(MethodInfo).GetMethod(nameof(MethodInfo.CreateDelegate), [typeof(Type), typeof(object)])!; |
|||
} |
|||
|
|||
public static readonly PropertyInfo AvaloniaObjectIndexer; |
|||
public static readonly MethodInfo CreateDelegateMethod; |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Diagnostics; |
|||
|
|||
[PrivateApi] |
|||
[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")] |
|||
public class StyleDiagnostics |
|||
{ |
|||
/// <summary>
|
|||
/// Currently applied styles.
|
|||
/// </summary>
|
|||
public IReadOnlyList<AppliedStyle> AppliedStyles { get; } |
|||
|
|||
public StyleDiagnostics(IReadOnlyList<AppliedStyle> appliedStyles) |
|||
{ |
|||
AppliedStyles = appliedStyles; |
|||
} |
|||
} |
|||
|
|||
[PrivateApi] |
|||
[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")] |
|||
public sealed class AppliedStyle |
|||
{ |
|||
private readonly StyleInstance _instance; |
|||
|
|||
internal AppliedStyle(StyleInstance instance) |
|||
{ |
|||
_instance = instance; |
|||
} |
|||
|
|||
public bool HasActivator => _instance.HasActivator; |
|||
public bool IsActive => _instance.IsActive(); |
|||
public StyleBase Style => (StyleBase)_instance.Source; |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Diagnostics; |
|||
|
|||
/// <summary>
|
|||
/// Defines diagnostic extensions on <see cref="StyledElement"/>s.
|
|||
/// </summary>
|
|||
[PrivateApi] |
|||
public static class StyledElementExtensions |
|||
{ |
|||
[Obsolete("Use AvaloniaObjectExtensions.GetValueStoreDiagnostic instead", true)] |
|||
public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement) |
|||
{ |
|||
var diagnostics = styledElement.GetValueStore().GetStoreDiagnostic(); |
|||
return new StyleDiagnostics(diagnostics.AppliedFrames |
|||
.OfType<StyleValueFrameDiagnostic>() |
|||
.Select(f => f.AsAppliedStyle()) |
|||
.ToArray()); |
|||
} |
|||
} |
|||
|
|||
@ -1,56 +1,11 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using Avalonia.Input.Platform; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public static class DataFormats |
|||
{ |
|||
/// <summary>
|
|||
/// Dataformat for plaintext
|
|||
/// </summary>
|
|||
[Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.Text)} instead.")] |
|||
public static readonly string Text = nameof(Text); |
|||
namespace Avalonia.Input; |
|||
|
|||
/// <summary>
|
|||
/// Dataformat for one or more files.
|
|||
/// </summary>
|
|||
[Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead.")] |
|||
public static readonly string Files = nameof(Files); |
|||
|
|||
/// <summary>
|
|||
/// Dataformat for one or more filenames
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This data format is supported only on desktop platforms.
|
|||
/// </remarks>
|
|||
[Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead."), EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static readonly string FileNames = nameof(FileNames); |
|||
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
|||
|
|||
internal static DataFormat ToDataFormat(string format) |
|||
{ |
|||
if (format == Text) |
|||
return DataFormat.Text; |
|||
|
|||
if (format == Files || format == FileNames) |
|||
return DataFormat.File; |
|||
|
|||
return DataFormat.CreateBytesPlatformFormat(format); |
|||
} |
|||
|
|||
internal static string ToString(DataFormat format) |
|||
{ |
|||
if (DataFormat.Text.Equals(format)) |
|||
return Text; |
|||
|
|||
if (DataFormat.File.Equals(format)) |
|||
return Files; |
|||
|
|||
return format.Identifier; |
|||
} |
|||
|
|||
#pragma warning restore CS0618 // Type or member is obsolete
|
|||
} |
|||
} |
|||
// TODO13: remove
|
|||
/// <summary>
|
|||
/// This class does not do anything anymore.
|
|||
/// Use <see cref="DataFormat"/> instead.
|
|||
/// </summary>
|
|||
[Obsolete($"Use {nameof(DataFormat)} instead", true)] |
|||
public static class DataFormats; |
|||
|
|||
@ -1,40 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Specific and mutable implementation of the IDataObject interface.
|
|||
/// </summary>
|
|||
[Obsolete($"Use {nameof(DataTransfer)} instead")] |
|||
public class DataObject : IDataObject |
|||
{ |
|||
private readonly Dictionary<string, object> _items = new(); |
|||
namespace Avalonia.Input; |
|||
|
|||
/// <inheritdoc />
|
|||
public bool Contains(string dataFormat) |
|||
{ |
|||
return _items.ContainsKey(dataFormat); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public object? Get(string dataFormat) |
|||
{ |
|||
return _items.TryGetValue(dataFormat, out var item) ? item : null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public IEnumerable<string> GetDataFormats() |
|||
{ |
|||
return _items.Keys; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a value to the internal store of the data object with <see cref="DataFormats"/> as a key.
|
|||
/// </summary>
|
|||
public void Set(string dataFormat, object value) |
|||
{ |
|||
_items[dataFormat] = value; |
|||
} |
|||
} |
|||
} |
|||
// TODO13: remove
|
|||
/// <summary>
|
|||
/// This class does not do anything anymore.
|
|||
/// Use <see cref="DataTransfer"/> instead.
|
|||
/// </summary>
|
|||
[Obsolete($"Use {nameof(DataTransfer)} instead", true)] |
|||
public sealed class DataObject; |
|||
|
|||
@ -1,54 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using Avalonia.Platform.Storage; |
|||
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
// TODO12: remove
|
|||
public static class DataObjectExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Returns a list of files if the DataObject contains files or filenames.
|
|||
/// <seealso cref="DataFormats.Files"/>.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// Collection of storage items - files or folders. If format isn't available, returns null.
|
|||
/// </returns>
|
|||
public static IEnumerable<IStorageItem>? GetFiles(this IDataObject dataObject) |
|||
{ |
|||
return dataObject.Get(DataFormats.Files) as IEnumerable<IStorageItem>; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a list of filenames if the DataObject contains filenames.
|
|||
/// <seealso cref="DataFormats.FileNames"/>
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// Collection of file names. If format isn't available, returns null.
|
|||
/// </returns>
|
|||
[System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static IEnumerable<string>? GetFileNames(this IDataObject dataObject) |
|||
{ |
|||
return (dataObject.Get(DataFormats.FileNames) as IEnumerable<string>) |
|||
?? dataObject.GetFiles()? |
|||
.Select(f => f.TryGetLocalPath()) |
|||
.Where(p => !string.IsNullOrEmpty(p)) |
|||
.OfType<string>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dragged text if the DataObject contains any text.
|
|||
/// <seealso cref="DataFormats.Text"/>
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// A text string. If format isn't available, returns null.
|
|||
/// </returns>
|
|||
public static string? GetText(this IDataObject dataObject) |
|||
{ |
|||
return dataObject.Get(DataFormats.Text) as string; |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Interface to access information about the data of a drag-and-drop operation.
|
|||
/// </summary>
|
|||
[Obsolete($"Use {nameof(IDataTransfer)} or {nameof(IAsyncDataTransfer)} instead")] |
|||
public interface IDataObject |
|||
{ |
|||
/// <summary>
|
|||
/// Lists all formats which are present in the DataObject.
|
|||
/// <seealso cref="DataFormats"/>
|
|||
/// </summary>
|
|||
IEnumerable<string> GetDataFormats(); |
|||
|
|||
/// <summary>
|
|||
/// Checks whether a given DataFormat is present in this object
|
|||
/// <seealso cref="DataFormats"/>
|
|||
/// </summary>
|
|||
bool Contains(string dataFormat); |
|||
|
|||
/// <summary>
|
|||
/// Tries to get the data of the given DataFormat.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// Object data. If format isn't available, returns null.
|
|||
/// </returns>
|
|||
object? Get(string dataFormat); |
|||
} |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Text; |
|||
using Avalonia.Compatibility; |
|||
|
|||
namespace Avalonia.Input.Platform; |
|||
|
|||
/// <summary>
|
|||
/// Wraps a legacy <see cref="IDataObject"/> into a <see cref="IDataTransferItem"/>.
|
|||
/// </summary>
|
|||
[Obsolete] |
|||
internal sealed class DataObjectToDataTransferItemWrapper( |
|||
IDataObject dataObject, |
|||
DataFormat[] formats, |
|||
string[] formatStrings) |
|||
: PlatformDataTransferItem |
|||
{ |
|||
private readonly IDataObject _dataObject = dataObject; |
|||
private readonly DataFormat[] _formats = formats; |
|||
private readonly string[] _formatStrings = formatStrings; |
|||
|
|||
protected override DataFormat[] ProvideFormats() |
|||
=> _formats; |
|||
|
|||
protected override object? TryGetRawCore(DataFormat format) |
|||
{ |
|||
var index = Array.IndexOf(Formats, format); |
|||
if (index < 0) |
|||
return null; |
|||
|
|||
// We should never have DataFormat.File here, it's been handled by DataObjectToDataTransferWrapper.
|
|||
Debug.Assert(!DataFormat.File.Equals(format)); |
|||
|
|||
var formatString = _formatStrings[index]; |
|||
var data = _dataObject.Get(formatString); |
|||
|
|||
if (DataFormat.Text.Equals(format)) |
|||
return Convert.ToString(data) ?? string.Empty; |
|||
|
|||
if (format is DataFormat<string>) |
|||
return Convert.ToString(data); |
|||
|
|||
if (format is DataFormat<byte[]>) |
|||
return ConvertLegacyDataToBytes(format, data); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private static byte[]? ConvertLegacyDataToBytes(DataFormat format, object? data) |
|||
{ |
|||
switch (data) |
|||
{ |
|||
case null: |
|||
return null; |
|||
|
|||
case byte[] bytes: |
|||
return bytes; |
|||
|
|||
case string str: |
|||
return OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsIOS() ? |
|||
Encoding.Unicode.GetBytes(str) : |
|||
Encoding.UTF8.GetBytes(str); |
|||
|
|||
case Stream stream: |
|||
var length = (int)(stream.Length - stream.Position); |
|||
var buffer = new byte[length]; |
|||
|
|||
stream.ReadExactly(buffer, 0, length); |
|||
return buffer; |
|||
|
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,95 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using Avalonia.Platform.Storage; |
|||
using Avalonia.Platform.Storage.FileIO; |
|||
|
|||
namespace Avalonia.Input.Platform; |
|||
|
|||
#pragma warning disable CS0618 // Type or member is obsolete: usages of IDataObject and DataFormats
|
|||
|
|||
// TODO12: remove
|
|||
/// <summary>
|
|||
/// Wraps a legacy <see cref="IDataObject"/> into a <see cref="IDataTransfer"/>.
|
|||
/// </summary>
|
|||
[Obsolete] |
|||
internal sealed class DataObjectToDataTransferWrapper(IDataObject dataObject) |
|||
: PlatformDataTransfer |
|||
{ |
|||
public IDataObject DataObject { get; } = dataObject; |
|||
|
|||
protected override DataFormat[] ProvideFormats() |
|||
=> DataObject.GetDataFormats().Select(DataFormats.ToDataFormat).Distinct().ToArray(); |
|||
|
|||
protected override PlatformDataTransferItem[] ProvideItems() |
|||
{ |
|||
var items = new List<PlatformDataTransferItem>(); |
|||
var nonFileFormats = new List<DataFormat>(); |
|||
var nonFileFormatStrings = new List<string>(); |
|||
var hasFiles = false; |
|||
|
|||
foreach (var formatString in DataObject.GetDataFormats()) |
|||
{ |
|||
var format = DataFormats.ToDataFormat(formatString); |
|||
|
|||
if (formatString == DataFormats.Files) |
|||
{ |
|||
if (hasFiles) |
|||
continue; |
|||
|
|||
// This is not ideal as we're reading the filenames ahead of time to generate the appropriate items.
|
|||
// We don't really care about that for this legacy wrapper.
|
|||
if (DataObject.Get(formatString) is IEnumerable<IStorageItem> storageItems) |
|||
{ |
|||
hasFiles = true; |
|||
|
|||
foreach (var storageItem in storageItems) |
|||
items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem)); |
|||
} |
|||
} |
|||
else if (formatString == DataFormats.FileNames) |
|||
{ |
|||
if (hasFiles) |
|||
continue; |
|||
|
|||
if (DataObject.Get(formatString) is IEnumerable<string> fileNames) |
|||
{ |
|||
hasFiles = true; |
|||
|
|||
foreach (var fileName in fileNames) |
|||
{ |
|||
if (StorageProviderHelpers.TryCreateBclStorageItem(fileName) is { } storageItem) |
|||
items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem)); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
nonFileFormats.Add(format); |
|||
nonFileFormatStrings.Add(formatString); |
|||
} |
|||
} |
|||
|
|||
if (nonFileFormats.Count > 0) |
|||
{ |
|||
Debug.Assert(nonFileFormats.Count == nonFileFormatStrings.Count); |
|||
|
|||
// Single item containing all formats except for DataFormat.File.
|
|||
items.Add(new DataObjectToDataTransferItemWrapper( |
|||
DataObject, |
|||
nonFileFormats.ToArray(), |
|||
nonFileFormatStrings.ToArray())); |
|||
} |
|||
|
|||
return items.ToArray(); |
|||
} |
|||
|
|||
[SuppressMessage( |
|||
"ReSharper", |
|||
"SuspiciousTypeConversion.Global", |
|||
Justification = "IDisposable may be implemented externally by the IDataObject instance.")] |
|||
public override void Dispose() |
|||
=> (DataObject as IDisposable)?.Dispose(); |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Platform.Storage; |
|||
|
|||
namespace Avalonia.Input.Platform; |
|||
|
|||
/// <summary>
|
|||
/// Wraps a <see cref="IDataTransfer"/> into a legacy <see cref="IDataObject"/>.
|
|||
/// </summary>
|
|||
[Obsolete] |
|||
internal sealed class DataTransferToDataObjectWrapper(IDataTransfer dataTransfer) : IDataObject |
|||
{ |
|||
public IDataTransfer DataTransfer { get; } = dataTransfer; |
|||
|
|||
public IEnumerable<string> GetDataFormats() |
|||
=> DataTransfer.Formats.Select(DataFormats.ToString); |
|||
|
|||
public bool Contains(string dataFormat) |
|||
=> DataTransfer.Contains(DataFormats.ToDataFormat(dataFormat)); |
|||
|
|||
public object? Get(string dataFormat) |
|||
{ |
|||
if (dataFormat == DataFormats.Text) |
|||
return DataTransfer.TryGetText(); |
|||
|
|||
if (dataFormat == DataFormats.Files) |
|||
return DataTransfer.TryGetFiles(); |
|||
|
|||
if (dataFormat == DataFormats.FileNames) |
|||
{ |
|||
return DataTransfer |
|||
.TryGetFiles() |
|||
?.Select(file => file.TryGetLocalPath()) |
|||
.Where(path => path is not null) |
|||
.ToArray(); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Rendering.Composition.Server; |
|||
|
|||
namespace Avalonia.Media; |
|||
|
|||
/// <summary>
|
|||
/// Represents the behavior of caching a visual element or tree of elements as bitmap surfaces.
|
|||
/// </summary>
|
|||
public class BitmapCache : CacheMode |
|||
{ |
|||
private CompositionBitmapCache? _current; |
|||
|
|||
public static readonly StyledProperty<double> RenderAtScaleProperty = AvaloniaProperty.Register<BitmapCache, double>( |
|||
nameof(RenderAtScale), 1); |
|||
|
|||
/// <summary>
|
|||
/// Use the RenderAtScale property to render the BitmapCache at a multiple of the normal bitmap size.
|
|||
/// The normal size is determined by the local size of the element.
|
|||
///
|
|||
/// Values greater than 1 increase the resolution of the bitmap relative to the native resolution of the element,
|
|||
/// and values less than 1 decrease the resolution.
|
|||
/// For example, if the RenderAtScale property is set to 2.0, and you apply a scale transform that
|
|||
/// enlarges the content by a factor of 2, the content will have the same visual quality as the same content
|
|||
/// with RenderAtScale set to 1.0 and a transform scale of 1.
|
|||
///
|
|||
/// When RenderAtScale is set to 0, no bitmap is rendered. Negative values are clamped to 0.
|
|||
///
|
|||
/// If you change this value, the cache is regenerated at the appropriate new resolution.
|
|||
/// </summary>
|
|||
public double RenderAtScale |
|||
{ |
|||
get => GetValue(RenderAtScaleProperty); |
|||
set => SetValue(RenderAtScaleProperty, value); |
|||
} |
|||
|
|||
public static readonly StyledProperty<bool> SnapsToDevicePixelsProperty = AvaloniaProperty.Register<BitmapCache, bool>( |
|||
nameof(SnapsToDevicePixels)); |
|||
|
|||
/// <summary>
|
|||
/// Set the SnapsToDevicePixels property when the cache displays content that requires pixel-alignment to render correctly.
|
|||
/// This is the case for text with subpixel antialiasing. If you set the EnableClearType property to true,
|
|||
/// consider setting SnapsToDevicePixels to true to ensure proper rendering.
|
|||
///
|
|||
/// When the SnapsToDevicePixels property is set to false,
|
|||
/// you can move and scale the cached element by a fraction of a pixel.
|
|||
///
|
|||
/// When the SnapsToDevicePixels property is set to true,
|
|||
/// the bitmap cache is aligned with pixel boundaries of the destination.
|
|||
/// If you move or scale the cached element by a fraction of a pixel,
|
|||
/// the bitmap snaps to the pixel grid
|
|||
/// . In this case, the top-left corner of the bitmap is rounded up and snapped to the pixel grid,
|
|||
/// but the bottom-right corner is on a fractional pixel boundary.
|
|||
/// </summary>
|
|||
public bool SnapsToDevicePixels |
|||
{ |
|||
get => GetValue(SnapsToDevicePixelsProperty); |
|||
set => SetValue(SnapsToDevicePixelsProperty, value); |
|||
} |
|||
|
|||
public static readonly StyledProperty<bool> EnableClearTypeProperty = AvaloniaProperty.Register<BitmapCache, bool>( |
|||
nameof(EnableClearType)); |
|||
|
|||
/// <summary>
|
|||
/// Set the EnableClearType property to allow subpixel text to be rendered in the cache.
|
|||
/// When the EnableClearType property is true, your application MUST render all
|
|||
/// of its subpixel text on an opaque background.
|
|||
///
|
|||
/// When the EnableClearType property is false, text in the cache is rendered with grayscale antialiasing.
|
|||
///
|
|||
/// ClearType text requires correct pixel alignment of rendered characters,
|
|||
/// so you should set the SnapsToDevicePixels property to true.
|
|||
/// If you do not set this property, the content may not blend correctly.
|
|||
///
|
|||
/// Use the EnableClearType property when you know the cache is rendered on pixel boundaries,
|
|||
/// so it is safe to cache ClearType text. This situation occurs commonly in text-scrolling scenarios.
|
|||
/// </summary>
|
|||
public bool EnableClearType |
|||
{ |
|||
get => GetValue(EnableClearTypeProperty); |
|||
set => SetValue(EnableClearTypeProperty, value); |
|||
} |
|||
|
|||
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
if (change.IsEffectiveValueChange && _current != null) |
|||
{ |
|||
if (change.Property == RenderAtScaleProperty) |
|||
_current.RenderAtScale = RenderAtScale; |
|||
else if (change.Property == SnapsToDevicePixelsProperty) |
|||
_current.SnapsToDevicePixels = SnapsToDevicePixels; |
|||
else if (change.Property == EnableClearTypeProperty) |
|||
_current.EnableClearType = EnableClearType; |
|||
} |
|||
|
|||
base.OnPropertyChanged(change); |
|||
} |
|||
|
|||
// We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
|
|||
internal override CompositionCacheMode GetForCompositor(Compositor c) |
|||
{ |
|||
// TODO: Make it to be a multi-compositor resource once we support visuals being attached to multiple
|
|||
// compositor instances (e. g. referenced via visual brush from a different WASM toplevel).
|
|||
if(_current?.Compositor != c) |
|||
{ |
|||
_current = new CompositionBitmapCache(c, new ServerCompositionBitmapCache(c.Server)); |
|||
_current.EnableClearType = EnableClearType; |
|||
_current.RenderAtScale = RenderAtScale; |
|||
_current.SnapsToDevicePixels = SnapsToDevicePixels; |
|||
} |
|||
|
|||
return _current; |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Rendering.Composition.Drawing; |
|||
|
|||
namespace Avalonia.Media; |
|||
|
|||
/// <summary>
|
|||
/// Represents cached content modes for graphics acceleration features.
|
|||
/// </summary>
|
|||
public abstract class CacheMode : StyledElement |
|||
{ |
|||
// We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
|
|||
internal abstract CompositionCacheMode GetForCompositor(Compositor c); |
|||
|
|||
public static CacheMode Parse(string s) |
|||
{ |
|||
if(s == "BitmapCache") |
|||
return new BitmapCache(); |
|||
throw new ArgumentException("Unknown CacheMode: " + s); |
|||
} |
|||
} |
|||
@ -1,8 +1,10 @@ |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition; |
|||
|
|||
internal interface ICompositionTargetDebugEvents |
|||
{ |
|||
int RenderedVisuals { get; } |
|||
void IncrementRenderedVisuals(); |
|||
void RectInvalidated(Rect rc); |
|||
int RenderedVisuals { get; set; } |
|||
int VisitedVisuals { get; set; } |
|||
void RectInvalidated(LtrbRect rc); |
|||
} |
|||
|
|||
@ -0,0 +1,41 @@ |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal class CompositorPools |
|||
{ |
|||
public class StackPool<T> : Stack<Stack<T>> |
|||
{ |
|||
public Stack<T> Rent() |
|||
{ |
|||
if (Count > 0) |
|||
return Pop()!; |
|||
return new Stack<T>(); |
|||
} |
|||
|
|||
public void Return(ref Stack<T> stack) |
|||
{ |
|||
Return(stack); |
|||
stack = null!; |
|||
} |
|||
|
|||
public void Return(Stack<T>? stack) |
|||
{ |
|||
if (stack == null) |
|||
return; |
|||
|
|||
stack.Clear(); |
|||
Push(stack); |
|||
} |
|||
} |
|||
|
|||
public StackPool<ServerCompositionVisual.TreeWalkerFrame> TreeWalkerFrameStackPool { get; } = new(); |
|||
public StackPool<Matrix> MatrixStackPool { get; } = new(); |
|||
public StackPool<LtrbRect> LtrbRectStackPool { get; } = new(); |
|||
public StackPool<double> DoubleStackPool { get; } = new(); |
|||
public StackPool<int> IntStackPool { get; } = new(); |
|||
public StackPool<IDirtyRectCollector> DirtyRectCollectorStackPool { get; } = new(); |
|||
|
|||
} |
|||
@ -1,105 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Numerics; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal interface IDirtyRectTracker |
|||
{ |
|||
void AddRect(LtrbPixelRect rect); |
|||
IDisposable BeginDraw(IDrawingContextImpl ctx); |
|||
bool IsEmpty { get; } |
|||
bool Intersects(LtrbRect rect); |
|||
bool Contains(Point pt); |
|||
void Reset(); |
|||
void Visualize(IDrawingContextImpl context); |
|||
LtrbPixelRect CombinedRect { get; } |
|||
IList<LtrbPixelRect> Rects { get; } |
|||
} |
|||
|
|||
internal class DirtyRectTracker : IDirtyRectTracker |
|||
{ |
|||
private LtrbPixelRect _rect; |
|||
private Rect _doubleRect; |
|||
private LtrbRect _normalRect; |
|||
private LtrbPixelRect[] _rectsForApi = new LtrbPixelRect[1]; |
|||
private Random _random = new(); |
|||
public void AddRect(LtrbPixelRect rect) |
|||
{ |
|||
_rect = _rect.Union(rect); |
|||
} |
|||
|
|||
public IDisposable BeginDraw(IDrawingContextImpl ctx) |
|||
{ |
|||
ctx.PushClip(_rect.ToRectWithNoScaling()); |
|||
_doubleRect = _rect.ToRectWithNoScaling(); |
|||
_normalRect = new(_doubleRect); |
|||
return Disposable.Create(ctx.PopClip); |
|||
} |
|||
|
|||
public bool IsEmpty => _rect.IsEmpty; |
|||
public bool Intersects(LtrbRect rect) => _normalRect.Intersects(rect); |
|||
public bool Contains(Point pt) => _rect.Contains((int)pt.X, (int)pt.Y); |
|||
|
|||
public void Reset() => _rect = default; |
|||
public void Visualize(IDrawingContextImpl context) |
|||
{ |
|||
context.DrawRectangle( |
|||
new ImmutableSolidColorBrush( |
|||
new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))), |
|||
null, _doubleRect); |
|||
} |
|||
|
|||
public LtrbPixelRect CombinedRect => _rect; |
|||
|
|||
public IList<LtrbPixelRect> Rects |
|||
{ |
|||
get |
|||
{ |
|||
if (_rect.IsEmpty) |
|||
return Array.Empty<LtrbPixelRect>(); |
|||
_rectsForApi[0] = _rect; |
|||
return _rectsForApi; |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal class RegionDirtyRectTracker : IDirtyRectTracker |
|||
{ |
|||
private readonly IPlatformRenderInterfaceRegion _region; |
|||
private Random _random = new(); |
|||
|
|||
public RegionDirtyRectTracker(IPlatformRenderInterface platformRender) |
|||
{ |
|||
_region = platformRender.CreateRegion(); |
|||
} |
|||
|
|||
public void AddRect(LtrbPixelRect rect) => _region.AddRect(rect); |
|||
|
|||
public IDisposable BeginDraw(IDrawingContextImpl ctx) |
|||
{ |
|||
ctx.PushClip(_region); |
|||
return Disposable.Create(ctx.PopClip); |
|||
} |
|||
|
|||
public bool IsEmpty => _region.IsEmpty; |
|||
public bool Intersects(LtrbRect rect) => _region.Intersects(rect); |
|||
public bool Contains(Point pt) => _region.Contains(pt); |
|||
|
|||
public void Reset() => _region.Reset(); |
|||
|
|||
public void Visualize(IDrawingContextImpl context) |
|||
{ |
|||
context.DrawRegion( |
|||
new ImmutableSolidColorBrush( |
|||
new Color(150, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))), |
|||
null, _region); |
|||
} |
|||
|
|||
public LtrbPixelRect CombinedRect => _region.Bounds; |
|||
public IList<LtrbPixelRect> Rects => _region.Rects; |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal class DebugEventsDirtyRectCollectorProxy(IDirtyRectCollector inner, ICompositionTargetDebugEvents events) |
|||
: IDirtyRectCollector |
|||
{ |
|||
public void AddRect(LtrbRect rect) |
|||
{ |
|||
inner.AddRect(rect); |
|||
events.RectInvalidated(rect); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal interface IDirtyRectTracker : IDirtyRectCollector |
|||
{ |
|||
/// <summary>
|
|||
/// Post-processes the dirty rect area (e. g. to account for anti-aliasing)
|
|||
/// </summary>
|
|||
void FinalizeFrame(LtrbRect bounds); |
|||
IDisposable BeginDraw(IDrawingContextImpl ctx); |
|||
bool IsEmpty { get; } |
|||
bool Intersects(LtrbRect rect); |
|||
void Initialize(LtrbRect bounds); |
|||
void Visualize(IDrawingContextImpl context); |
|||
LtrbRect CombinedRect { get; } |
|||
} |
|||
|
|||
internal interface IDirtyRectCollector |
|||
{ |
|||
void AddRect(LtrbRect rect); |
|||
} |
|||
@ -0,0 +1,348 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
partial class MultiDirtyRectTracker |
|||
{ |
|||
/// <summary>
|
|||
/// This is a port of CDirtyRegion2 from WPF
|
|||
/// </summary>
|
|||
class CDirtyRegion2(int MaxDirtyRegionCount) |
|||
{ |
|||
|
|||
private readonly LtrbRect[] _dirtyRegions = new LtrbRect[MaxDirtyRegionCount]; |
|||
private readonly LtrbRect[] _resolvedRegions = new LtrbRect[MaxDirtyRegionCount]; |
|||
private readonly double[,] _overhead = new double[MaxDirtyRegionCount + 1, MaxDirtyRegionCount]; |
|||
private LtrbRect _surfaceBounds; |
|||
private double _allowedDirtyRegionOverhead; |
|||
private int _regionCount; |
|||
private bool _optimized; |
|||
private bool _maxSurfaceFallback; |
|||
|
|||
private readonly struct UnionResult |
|||
{ |
|||
public readonly double Overhead; |
|||
// Left here for debugging purposes
|
|||
public readonly double Area; |
|||
public readonly LtrbRect Union; |
|||
|
|||
public UnionResult(double overhead, double area, LtrbRect union) |
|||
{ |
|||
Overhead = overhead; |
|||
Area = area; |
|||
Union = union; |
|||
} |
|||
} |
|||
|
|||
private static double RectArea(LtrbRect r) |
|||
{ |
|||
return (r.Right - r.Left) * (r.Bottom - r.Top); |
|||
} |
|||
|
|||
private static LtrbRect RectUnion(LtrbRect left, LtrbRect right) |
|||
{ |
|||
if (left.IsZeroSize) |
|||
return right; |
|||
if (right.IsZeroSize) |
|||
return left; |
|||
return left.Union(right); |
|||
} |
|||
|
|||
private static UnionResult ComputeUnion(LtrbRect r0, LtrbRect r1) |
|||
{ |
|||
var unioned = RectUnion(r0, r1); |
|||
var intersected = r0.IntersectOrEmpty(r1); |
|||
|
|||
double areaOfUnion = RectArea(unioned); |
|||
double overhead = areaOfUnion - (RectArea(r0) + RectArea(r1) - RectArea(intersected)); |
|||
|
|||
|
|||
// Use 0 as overhead if computed overhead is negative or overhead
|
|||
// computation returns a nan. (If more than one of the previous
|
|||
// area computations overflowed then overhead could be not a
|
|||
// number.)
|
|||
if (!(overhead > 0)) |
|||
{ |
|||
overhead = 0; |
|||
} |
|||
|
|||
return new UnionResult(overhead, areaOfUnion, unioned); |
|||
} |
|||
|
|||
private void SetOverhead(int i, int j, double value) |
|||
{ |
|||
if (i > j) |
|||
{ |
|||
_overhead[i, j] = value; |
|||
} |
|||
else if (i < j) |
|||
{ |
|||
_overhead[j, i] = value; |
|||
} |
|||
} |
|||
|
|||
private double GetOverhead(int i, int j) |
|||
{ |
|||
if (i > j) |
|||
{ |
|||
return _overhead[i, j]; |
|||
} |
|||
|
|||
if (i < j) |
|||
{ |
|||
return _overhead[j, i]; |
|||
} |
|||
|
|||
return double.MaxValue; |
|||
} |
|||
|
|||
private void UpdateOverhead(int regionIndex) |
|||
{ |
|||
ref readonly var regionAtIndex = ref _dirtyRegions[regionIndex]; |
|||
for (int i = 0; i < MaxDirtyRegionCount; i++) |
|||
{ |
|||
if (regionIndex != i) |
|||
{ |
|||
var ur = ComputeUnion(_dirtyRegions[i], regionAtIndex); |
|||
SetOverhead(i, regionIndex, ur.Overhead); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initialize must be called before adding dirty rects. Initialize can also be called to
|
|||
/// reset the dirty region.
|
|||
/// </summary>
|
|||
public void Initialize(LtrbRect surfaceBounds, double allowedDirtyRegionOverhead) |
|||
{ |
|||
_allowedDirtyRegionOverhead = allowedDirtyRegionOverhead; |
|||
Array.Clear(_dirtyRegions); |
|||
Array.Clear(_overhead); |
|||
_optimized = false; |
|||
_maxSurfaceFallback = false; |
|||
_regionCount = 0; |
|||
|
|||
_surfaceBounds = surfaceBounds; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new dirty rectangle to the dirty region.
|
|||
/// </summary>
|
|||
public void Add(LtrbRect newRegion) |
|||
{ |
|||
|
|||
// // We've already fallen back to setting the whole surface as a dirty region
|
|||
// // because of invalid dirty rects, so no need to add any new ones
|
|||
if (_maxSurfaceFallback) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// // Check if rectangle is well formed before we try to intersect it,
|
|||
// // because Intersect will fail for badly formed rects
|
|||
if (!newRegion.IsWellOrdered) |
|||
{ |
|||
// If we're here it means that we've been passed an invalid rectangle as a dirty
|
|||
// region, containing NAN or a non well ordered rectangle.
|
|||
// In this case, make the dirty region the full surface size and warn in the debugger
|
|||
// since this could cause a serious perf regression.
|
|||
//
|
|||
Debug.Assert(false); |
|||
|
|||
//
|
|||
// Remove all dirty regions from this object, since
|
|||
// they're no longer relevant.
|
|||
//
|
|||
Initialize(_surfaceBounds, _allowedDirtyRegionOverhead); |
|||
_maxSurfaceFallback = true; |
|||
_regionCount = 1; |
|||
return; |
|||
} |
|||
|
|||
var clippedNewRegion = newRegion.IntersectOrEmpty(_surfaceBounds); |
|||
|
|||
if (clippedNewRegion.IsEmpty) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Always keep bounding boxes device space integer.
|
|||
clippedNewRegion = new LtrbRect( |
|||
Math.Floor(clippedNewRegion.Left), |
|||
Math.Floor(clippedNewRegion.Top), |
|||
Math.Ceiling(clippedNewRegion.Right), |
|||
Math.Ceiling(clippedNewRegion.Bottom)); |
|||
|
|||
// Compute the overhead for the new region combined with all existing regions
|
|||
for (int n = 0; n < MaxDirtyRegionCount; n++) |
|||
{ |
|||
var ur = ComputeUnion(_dirtyRegions[n], clippedNewRegion); |
|||
SetOverhead(MaxDirtyRegionCount, n, ur.Overhead); |
|||
} |
|||
|
|||
// Find the pair of dirty regions that if merged create the minimal overhead. A overhead
|
|||
// of 0 is perfect in the sense that it can not get better. In that case we break early
|
|||
// out of the loop.
|
|||
double minimalOverhead = double.MaxValue; |
|||
int bestMatchN = 0; |
|||
int bestMatchK = 0; |
|||
bool matchFound = false; |
|||
|
|||
for (int n = MaxDirtyRegionCount; n > 0; n--) |
|||
{ |
|||
for (int k = 0; k < n; k++) |
|||
{ |
|||
double overheadNK = GetOverhead(n, k); |
|||
if (minimalOverhead >= overheadNK) |
|||
{ |
|||
minimalOverhead = overheadNK; |
|||
bestMatchN = n; |
|||
bestMatchK = k; |
|||
matchFound = true; |
|||
|
|||
if (overheadNK < _allowedDirtyRegionOverhead) |
|||
{ |
|||
// If the overhead is very small, we bail out early since this
|
|||
// saves us some valuable cycles. Note that "small" means really
|
|||
// nothing here. In fact we don't always know if that number is
|
|||
// actually small. However, it the algorithm stays still correct
|
|||
// in the sense that we render everything that is necessary. It
|
|||
// might just be not optimal.
|
|||
goto LoopExit; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!matchFound) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
LoopExit: |
|||
|
|||
// Case A: The new dirty region can be combined with an existing one
|
|||
if (bestMatchN == MaxDirtyRegionCount) |
|||
{ |
|||
var ur = ComputeUnion(clippedNewRegion, _dirtyRegions[bestMatchK]); |
|||
var unioned = ur.Union; |
|||
|
|||
if (_dirtyRegions[bestMatchK].Contains(unioned)) |
|||
{ |
|||
// newDirtyRegion is enclosed by dirty region bestMatchK
|
|||
return; |
|||
} |
|||
|
|||
_dirtyRegions[bestMatchK] = unioned; |
|||
UpdateOverhead(bestMatchK); |
|||
} |
|||
else |
|||
{ |
|||
// Case B: Merge region N with region K, store new region slot K
|
|||
var ur = ComputeUnion(_dirtyRegions[bestMatchN], _dirtyRegions[bestMatchK]); |
|||
_dirtyRegions[bestMatchN] = ur.Union; |
|||
_dirtyRegions[bestMatchK] = clippedNewRegion; |
|||
UpdateOverhead(bestMatchN); |
|||
UpdateOverhead(bestMatchK); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns an array of dirty rectangles describing the dirty region.
|
|||
/// </summary>
|
|||
public ReadOnlySpan<LtrbRect> GetUninflatedDirtyRegions() |
|||
{ |
|||
if (_maxSurfaceFallback) |
|||
{ |
|||
return new ReadOnlySpan<LtrbRect>(in _surfaceBounds); |
|||
} |
|||
|
|||
if (!_optimized) |
|||
{ |
|||
Array.Clear(_resolvedRegions); |
|||
|
|||
// Consolidate the dirtyRegions array
|
|||
int addedDirtyRegionCount = 0; |
|||
for (int i = 0; i < MaxDirtyRegionCount; i++) |
|||
{ |
|||
if (!_dirtyRegions[i].IsEmpty) |
|||
{ |
|||
if (i != addedDirtyRegionCount) |
|||
{ |
|||
_dirtyRegions[addedDirtyRegionCount] = _dirtyRegions[i]; |
|||
UpdateOverhead(addedDirtyRegionCount); |
|||
} |
|||
|
|||
addedDirtyRegionCount++; |
|||
} |
|||
} |
|||
|
|||
// Merge all dirty rects that we can
|
|||
bool couldMerge = true; |
|||
while (couldMerge) |
|||
{ |
|||
couldMerge = false; |
|||
for (int n = 0; n < addedDirtyRegionCount; n++) |
|||
{ |
|||
for (int k = n + 1; k < addedDirtyRegionCount; k++) |
|||
{ |
|||
if (!_dirtyRegions[n].IsEmpty |
|||
&& !_dirtyRegions[k].IsEmpty |
|||
&& GetOverhead(n, k) < _allowedDirtyRegionOverhead) |
|||
{ |
|||
var ur = ComputeUnion(_dirtyRegions[n], _dirtyRegions[k]); |
|||
_dirtyRegions[n] = ur.Union; |
|||
_dirtyRegions[k] = default; |
|||
UpdateOverhead(n); |
|||
couldMerge = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Consolidate and copy into resolvedRegions
|
|||
int finalRegionCount = 0; |
|||
for (int i = 0; i < addedDirtyRegionCount; i++) |
|||
{ |
|||
if (!_dirtyRegions[i].IsEmpty) |
|||
{ |
|||
_resolvedRegions[finalRegionCount] = _dirtyRegions[i]; |
|||
finalRegionCount++; |
|||
} |
|||
} |
|||
|
|||
_regionCount = finalRegionCount; |
|||
_optimized = true; |
|||
} |
|||
|
|||
return _resolvedRegions.AsSpan(0, _regionCount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if the dirty region is empty.
|
|||
/// </summary>
|
|||
public bool IsEmpty |
|||
{ |
|||
get |
|||
{ |
|||
for (int i = 0; i < MaxDirtyRegionCount; i++) |
|||
{ |
|||
if (!_dirtyRegions[i].IsEmpty) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dirty region count.
|
|||
/// NOTE: The region count is NOT VALID until GetUninflatedDirtyRegions is called.
|
|||
/// </summary>
|
|||
public int RegionCount => _regionCount; |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
|
|||
internal partial class MultiDirtyRectTracker : IDirtyRectTracker |
|||
{ |
|||
private readonly double _maxOverhead; |
|||
private readonly CDirtyRegion2 _regions; |
|||
private readonly IPlatformRenderInterfaceRegion _clipRegion; |
|||
private readonly List<LtrbRect> _inflatedRects = new(); |
|||
private Random _random = new(); |
|||
|
|||
public MultiDirtyRectTracker(IPlatformRenderInterface platformRender, int maxDirtyRects, double maxOverhead) |
|||
{ |
|||
_maxOverhead = maxOverhead; |
|||
_regions = new CDirtyRegion2(maxDirtyRects); |
|||
_clipRegion = platformRender.CreateRegion(); |
|||
} |
|||
|
|||
public void AddRect(LtrbRect rect) => _regions.Add(rect); |
|||
|
|||
public void FinalizeFrame(LtrbRect bounds) |
|||
{ |
|||
_inflatedRects.Clear(); |
|||
_clipRegion.Reset(); |
|||
|
|||
var dirtyRegions = _regions.GetUninflatedDirtyRegions(); |
|||
|
|||
LtrbRect? combined = default; |
|||
foreach (var rect in dirtyRegions) |
|||
{ |
|||
var inflated = rect.Inflate(new(1)).IntersectOrEmpty(bounds); |
|||
_inflatedRects.Add(inflated); |
|||
_clipRegion.AddRect(LtrbPixelRect.FromRectUnscaled(inflated)); |
|||
combined = LtrbRect.FullUnion(combined, inflated); |
|||
} |
|||
|
|||
CombinedRect = combined ?? default; |
|||
} |
|||
|
|||
public IDisposable BeginDraw(IDrawingContextImpl ctx) |
|||
{ |
|||
ctx.PushClip(_clipRegion); |
|||
return Disposable.Create(ctx.PopClip); |
|||
} |
|||
|
|||
public bool IsEmpty => _regions.IsEmpty; |
|||
|
|||
public bool Intersects(LtrbRect rect) |
|||
{ |
|||
foreach(var r in _inflatedRects) |
|||
{ |
|||
if (r.Intersects(rect)) |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void Initialize(LtrbRect bounds) |
|||
{ |
|||
|
|||
_regions.Initialize(bounds, _maxOverhead); |
|||
_inflatedRects.Clear(); |
|||
_clipRegion.Reset(); |
|||
CombinedRect = default; |
|||
} |
|||
|
|||
public void Visualize(IDrawingContextImpl context) |
|||
{ |
|||
context.DrawRegion( |
|||
new ImmutableSolidColorBrush( |
|||
new Color(150, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))), |
|||
null, _clipRegion); |
|||
} |
|||
|
|||
public LtrbRect CombinedRect { get; private set; } |
|||
|
|||
public IReadOnlyList<LtrbRect> InflatedRects => _inflatedRects; |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal class RegionDirtyRectTracker : IDirtyRectTracker |
|||
{ |
|||
private readonly IPlatformRenderInterfaceRegion _region; |
|||
private readonly List<LtrbRect> _rects = new(); |
|||
private Random _random = new(); |
|||
|
|||
public RegionDirtyRectTracker(IPlatformRenderInterface platformRender) |
|||
{ |
|||
_region = platformRender.CreateRegion(); |
|||
} |
|||
|
|||
public void AddRect(LtrbRect rect) => _rects.Add(rect); |
|||
|
|||
private LtrbPixelRect GetInflatedPixelRect(LtrbRect rc) |
|||
{ |
|||
var inflated = rc.Inflate(new Thickness(1)).IntersectOrEmpty(rc); |
|||
var pixelRect = LtrbPixelRect.FromRectUnscaled(inflated); |
|||
return pixelRect; |
|||
} |
|||
|
|||
public void FinalizeFrame(LtrbRect bounds) |
|||
{ |
|||
_region.Reset(); |
|||
foreach (var rc in _rects) |
|||
_region.AddRect(GetInflatedPixelRect(rc)); |
|||
CombinedRect = _region.Bounds.ToLtrbRectUnscaled(); |
|||
} |
|||
|
|||
public IDisposable BeginDraw(IDrawingContextImpl ctx) |
|||
{ |
|||
ctx.PushClip(_region); |
|||
return Disposable.Create(ctx.PopClip); |
|||
} |
|||
|
|||
public bool IsEmpty => _rects.Count == 0; |
|||
|
|||
public bool Intersects(LtrbRect rect) => _region.Intersects(rect); |
|||
|
|||
public void Initialize(LtrbRect bounds) => _rects.Clear(); |
|||
|
|||
public void Visualize(IDrawingContextImpl context) |
|||
{ |
|||
context.DrawRegion( |
|||
new ImmutableSolidColorBrush( |
|||
new Color(150, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))), |
|||
null, _region); |
|||
} |
|||
|
|||
public LtrbRect CombinedRect { get; private set; } |
|||
|
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Numerics; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal class SingleDirtyRectTracker : IDirtyRectTracker |
|||
{ |
|||
private LtrbRect? _rect; |
|||
private LtrbRect _extendedRect; |
|||
|
|||
private readonly Random _random = new(); |
|||
public void AddRect(LtrbRect rect) |
|||
{ |
|||
_rect = LtrbRect.FullUnion(_rect, rect); |
|||
} |
|||
|
|||
public void FinalizeFrame(LtrbRect bounds) |
|||
{ |
|||
|
|||
_extendedRect = _rect.HasValue |
|||
? LtrbPixelRect.FromRectUnscaled(_rect.Value.Inflate(new Thickness(1)).IntersectOrEmpty(bounds)) |
|||
.ToLtrbRectUnscaled() |
|||
: default; |
|||
} |
|||
|
|||
public IDisposable BeginDraw(IDrawingContextImpl ctx) |
|||
{ |
|||
ctx.PushClip(_extendedRect.ToRect()); |
|||
return Disposable.Create(ctx.PopClip); |
|||
} |
|||
|
|||
public bool IsEmpty => _rect?.IsZeroSize ?? true; |
|||
public bool Intersects(LtrbRect rect) => _extendedRect.Intersects(rect); |
|||
|
|||
public void Initialize(LtrbRect bounds) => _rect = default; |
|||
public void Visualize(IDrawingContextImpl context) |
|||
{ |
|||
context.DrawRectangle( |
|||
new ImmutableSolidColorBrush( |
|||
new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))), |
|||
null, _extendedRect.ToRect()); |
|||
} |
|||
|
|||
public LtrbRect CombinedRect => _extendedRect; |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal partial class ServerCompositionBitmapCache |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
partial class ServerCompositionCacheMode |
|||
{ |
|||
private readonly WeakHashList<ServerCompositionVisual> _attachedVisuals = new(); |
|||
|
|||
public void Subscribe(ServerCompositionVisual visual) => _attachedVisuals.Add(visual); |
|||
|
|||
public void Unsubscribe(ServerCompositionVisual visual) => _attachedVisuals.Remove(visual); |
|||
|
|||
protected override void ValuesInvalidated() |
|||
{ |
|||
using var alive = _attachedVisuals.GetAlive(); |
|||
if (alive != null) |
|||
{ |
|||
foreach (var v in alive.Span) |
|||
v.OnCacheModeStateChanged(); |
|||
} |
|||
|
|||
base.ValuesInvalidated(); |
|||
} |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Collections.Pooled; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal partial class ServerCompositionTarget |
|||
{ |
|||
public readonly IDirtyRectTracker DirtyRects; |
|||
|
|||
static int Clamp0(int value, int max) => Math.Max(Math.Min(value, max), 0); |
|||
|
|||
public void AddDirtyRect(LtrbRect rect) |
|||
{ |
|||
if (rect.IsZeroSize) |
|||
return; |
|||
|
|||
DebugEvents?.RectInvalidated(rect.ToRect()); |
|||
|
|||
var snapped = LtrbPixelRect.FromRectWithNoScaling(SnapToDevicePixels(rect, Scaling)); |
|||
|
|||
var clamped = new LtrbPixelRect( |
|||
Clamp0(snapped.Left, _pixelSize.Width), |
|||
Clamp0(snapped.Top, _pixelSize.Height), |
|||
Clamp0(snapped.Right, _pixelSize.Width), |
|||
Clamp0(snapped.Bottom, _pixelSize.Height) |
|||
); |
|||
|
|||
if (!clamped.IsEmpty) |
|||
DirtyRects.AddRect(clamped); |
|||
_redrawRequested = true; |
|||
} |
|||
|
|||
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(new(rect), Scaling).ToRect(); |
|||
public LtrbRect SnapToDevicePixels(LtrbRect rect) => SnapToDevicePixels(rect, Scaling); |
|||
|
|||
public static LtrbRect SnapToDevicePixels(LtrbRect rect, double scale) |
|||
{ |
|||
return new LtrbRect( |
|||
Math.Floor(rect.Left * scale) / scale, |
|||
Math.Floor(rect.Top * scale) / scale, |
|||
Math.Ceiling(rect.Right * scale) / scale, |
|||
Math.Ceiling(rect.Bottom * scale) / scale); |
|||
} |
|||
|
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue