committed by
GitHub
215 changed files with 10058 additions and 2992 deletions
@ -1,45 +1,52 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.ImagePage"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h1">Image</TextBlock> |
|||
<TextBlock Classes="h2">Displays an image</TextBlock> |
|||
|
|||
<StackPanel Orientation="Horizontal" |
|||
Margin="0,16,0,0" |
|||
HorizontalAlignment="Center" |
|||
Spacing="16"> |
|||
<StackPanel Orientation="Vertical"> |
|||
<TextBlock>No Stretch</TextBlock> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" |
|||
Width="100" Height="200" |
|||
Stretch="None"/> |
|||
</StackPanel> |
|||
|
|||
<StackPanel Orientation="Vertical"> |
|||
<TextBlock>Fill</TextBlock> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" |
|||
Width="100" Height="200" |
|||
Stretch="Fill"/> |
|||
</StackPanel> |
|||
<DockPanel> |
|||
<StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h1">Image</TextBlock> |
|||
<TextBlock Classes="h2">Displays an image</TextBlock> |
|||
</StackPanel> |
|||
|
|||
<StackPanel Orientation="Vertical"> |
|||
<TextBlock>Uniform</TextBlock> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" |
|||
Width="100" Height="200" |
|||
Stretch="Uniform"/> |
|||
</StackPanel> |
|||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64"> |
|||
|
|||
<DockPanel Grid.Column="0" Grid.Row="1" Margin="16"> |
|||
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock> |
|||
<ComboBox Name="bitmapStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapStretchChanged"> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
<ComboBoxItem>Fill</ComboBoxItem> |
|||
<ComboBoxItem>Uniform</ComboBoxItem> |
|||
<ComboBoxItem>UniformToFill</ComboBoxItem> |
|||
</ComboBox> |
|||
<Image Name="bitmapImage" |
|||
Source="/Assets/delicate-arch-896885_640.jpg"/> |
|||
</DockPanel> |
|||
|
|||
<StackPanel Orientation="Vertical"> |
|||
<TextBlock>UniformToFill</TextBlock> |
|||
<Image Source="/Assets/delicate-arch-896885_640.jpg" |
|||
Width="100" Height="200" |
|||
Stretch="UniformToFill"/> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
<StackPanel Orientation="Vertical"> |
|||
<TextBlock>Window Icon as an Image</TextBlock> |
|||
<Image Name="Icon" Width="100" Height="200" Stretch="None" /> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
<DockPanel Grid.Column="1" Grid.Row="1" Margin="16"> |
|||
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock> |
|||
<ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged"> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
<ComboBoxItem>Fill</ComboBoxItem> |
|||
<ComboBoxItem>Uniform</ComboBoxItem> |
|||
<ComboBoxItem>UniformToFill</ComboBoxItem> |
|||
</ComboBox> |
|||
<Image Name="drawingImage"> |
|||
<Image.Source> |
|||
<DrawingImage> |
|||
<GeometryDrawing Brush="Red"> |
|||
<PathGeometry> |
|||
<PathFigure StartPoint="0,0" IsClosed="True"> |
|||
<QuadraticBezierSegment Point1="50,0" Point2="50,-50" /> |
|||
<QuadraticBezierSegment Point1="100,-50" Point2="100,0" /> |
|||
<LineSegment Point="50,0" /> |
|||
<LineSegment Point="50,50" /> |
|||
</PathFigure> |
|||
</PathGeometry> |
|||
</GeometryDrawing> |
|||
</DrawingImage> |
|||
</Image.Source> |
|||
</Image> |
|||
</DockPanel> |
|||
</Grid> |
|||
|
|||
</DockPanel> |
|||
</UserControl> |
|||
|
|||
@ -1,64 +0,0 @@ |
|||
function Get-NewDirectoryName { |
|||
param ([System.IO.DirectoryInfo]$item) |
|||
|
|||
$name = $item.Name.Replace("perspex", "avalonia") |
|||
$name = $name.Replace("Perspex", "Avalonia") |
|||
Join-Path $item.Parent.FullName $name |
|||
} |
|||
|
|||
function Get-NewFileName { |
|||
param ([System.IO.FileInfo]$item) |
|||
|
|||
$name = $item.Name.Replace("perspex", "avalonia") |
|||
$name = $name.Replace("Perspex", "Avalonia") |
|||
Join-Path $item.DirectoryName $name |
|||
} |
|||
|
|||
function Rename-Contents { |
|||
param ([System.IO.FileInfo] $file) |
|||
|
|||
$extensions = @(".cs",".xaml",".csproj",".sln",".md",".json",".yml",".partial",".ps1",".nuspec",".htm",".html",".gitmodules".".xml",".plist",".targets",".projitems",".shproj",".xib") |
|||
|
|||
if ($extensions.Contains($file.Extension)) { |
|||
$text = [IO.File]::ReadAllText($file.FullName) |
|||
$text = $text.Replace("github.com/perspex", "github.com/avaloniaui") |
|||
$text = $text.Replace("github.com/Perspex", "github.com/AvaloniaUI") |
|||
$text = $text.Replace("perspex", "avalonia") |
|||
$text = $text.Replace("Perspex", "Avalonia") |
|||
$text = $text.Replace("PERSPEX", "AVALONIA") |
|||
[IO.File]::WriteAllText($file.FullName, $text) |
|||
} |
|||
} |
|||
|
|||
function Process-Files { |
|||
param ([System.IO.DirectoryInfo] $item) |
|||
|
|||
$dirs = Get-ChildItem -Path $item.FullName -Directory |
|||
$files = Get-ChildItem -Path $item.FullName -File |
|||
|
|||
foreach ($dir in $dirs) { |
|||
Process-Files $dir.FullName |
|||
} |
|||
|
|||
foreach ($file in $files) { |
|||
Rename-Contents $file |
|||
|
|||
$renamed = Get-NewFileName $file |
|||
|
|||
if ($file.FullName -ne $renamed) { |
|||
Write-Host git mv $file.FullName $renamed |
|||
& git mv $file.FullName $renamed |
|||
} |
|||
} |
|||
|
|||
$renamed = Get-NewDirectoryName $item |
|||
|
|||
if ($item.FullName -ne $renamed) { |
|||
Write-Host git mv $item.FullName $renamed |
|||
& git mv $item.FullName $renamed |
|||
} |
|||
} |
|||
|
|||
& git submodule deinit . |
|||
& git clean -xdf |
|||
Process-Files . |
|||
@ -0,0 +1,67 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Provides information for a avalonia property change.
|
|||
/// </summary>
|
|||
public class AvaloniaPropertyChangedEventArgs<T> : AvaloniaPropertyChangedEventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvaloniaPropertyChangedEventArgs"/> class.
|
|||
/// </summary>
|
|||
/// <param name="sender">The object that the property changed on.</param>
|
|||
/// <param name="property">The property that changed.</param>
|
|||
/// <param name="oldValue">The old value of the property.</param>
|
|||
/// <param name="newValue">The new value of the property.</param>
|
|||
/// <param name="priority">The priority of the binding that produced the value.</param>
|
|||
public AvaloniaPropertyChangedEventArgs( |
|||
IAvaloniaObject sender, |
|||
AvaloniaProperty<T> property, |
|||
Optional<T> oldValue, |
|||
BindingValue<T> newValue, |
|||
BindingPriority priority) |
|||
: base(sender, priority) |
|||
{ |
|||
Property = property; |
|||
OldValue = oldValue; |
|||
NewValue = newValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the property that changed.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The property that changed.
|
|||
/// </value>
|
|||
public new AvaloniaProperty<T> Property { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the old value of the property.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The old value of the property.
|
|||
/// </value>
|
|||
public new Optional<T> OldValue { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the new value of the property.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The new value of the property.
|
|||
/// </value>
|
|||
public new BindingValue<T> NewValue { get; private set; } |
|||
|
|||
protected override AvaloniaProperty GetProperty() => Property; |
|||
|
|||
protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue); |
|||
|
|||
protected override object? GetNewValue() => NewValue.GetValueOrDefault(AvaloniaProperty.UnsetValue); |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Represents boxed value of type <typeparamref name="T"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Type of stored value.</typeparam>
|
|||
internal readonly struct BoxedValue<T> |
|||
{ |
|||
public BoxedValue(T value) |
|||
{ |
|||
Boxed = value; |
|||
Typed = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Boxed value.
|
|||
/// </summary>
|
|||
public object Boxed { get; } |
|||
|
|||
/// <summary>
|
|||
/// Typed value.
|
|||
/// </summary>
|
|||
public T Typed { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// This source file is adapted from the Collections.Pooled.
|
|||
// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
|
|||
|
|||
namespace Avalonia.Collections.Pooled |
|||
{ |
|||
/// <summary>
|
|||
/// This enum allows control over how data is treated when internal
|
|||
/// arrays are returned to the ArrayPool. Be careful to understand
|
|||
/// what each option does before using anything other than the default
|
|||
/// of Auto.
|
|||
/// </summary>
|
|||
public enum ClearMode |
|||
{ |
|||
/// <summary>
|
|||
/// <para><code>Auto</code> has different behavior depending on the host project's target framework.</para>
|
|||
/// <para>.NET Core 2.1: Reference types and value types that contain reference types are cleared
|
|||
/// when the internal arrays are returned to the pool. Value types that do not contain reference
|
|||
/// types are not cleared when returned to the pool.</para>
|
|||
/// <para>.NET Standard 2.0: All user types are cleared before returning to the pool, in case they
|
|||
/// contain reference types.
|
|||
/// For .NET Standard, Auto and Always have the same behavior.</para>
|
|||
/// </summary>
|
|||
Auto = 0, |
|||
/// <summary>
|
|||
/// The <para><code>Always</code> setting has the effect of always clearing user types before returning to the pool.
|
|||
/// This is the default behavior on .NET Standard.</para><para>You might want to turn this on in a .NET Core project
|
|||
/// if you were concerned about sensitive data stored in value types leaking to other pars of your application.</para>
|
|||
/// </summary>
|
|||
Always = 1, |
|||
/// <summary>
|
|||
/// <para><code>Never</code> will cause pooled collections to never clear user types before returning them to the pool.</para>
|
|||
/// <para>You might want to use this setting in a .NET Standard project when you know that a particular collection stores
|
|||
/// only value types and you want the performance benefit of not taking time to reset array items to their default value.</para>
|
|||
/// <para>Be careful with this setting: if used for a collection that contains reference types, or value types that contain
|
|||
/// reference types, this setting could cause memory issues by making the garbage collector unable to clean up instances
|
|||
/// that are still being referenced by arrays sitting in the ArrayPool.</para>
|
|||
/// </summary>
|
|||
Never = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
|
|||
namespace Avalonia.Collections.Pooled |
|||
{ |
|||
internal sealed class ICollectionDebugView<T> |
|||
{ |
|||
private readonly ICollection<T> _collection; |
|||
|
|||
public ICollectionDebugView(ICollection<T> collection) |
|||
{ |
|||
_collection = collection ?? throw new ArgumentNullException(nameof(collection)); |
|||
} |
|||
|
|||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
|||
public T[] Items |
|||
{ |
|||
get |
|||
{ |
|||
T[] items = new T[_collection.Count]; |
|||
_collection.CopyTo(items, 0); |
|||
return items; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// This source file is adapted from the Collections.Pooled.
|
|||
// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Collections.Pooled |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a read-only collection of pooled elements that can be accessed by index
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of elements in the read-only pooled list.</typeparam>
|
|||
|
|||
public interface IReadOnlyPooledList<T> : IReadOnlyList<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a <see cref="System.ReadOnlySpan{T}"/> for the items currently in the collection.
|
|||
/// </summary>
|
|||
ReadOnlySpan<T> Span { get; } |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,699 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
/*============================================================================= |
|||
** |
|||
** |
|||
** Purpose: An array implementation of a generic stack. |
|||
** |
|||
** |
|||
=============================================================================*/ |
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Serialization; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.Collections.Pooled |
|||
{ |
|||
/// <summary>
|
|||
/// A simple stack of objects. Internally it is implemented as an array,
|
|||
/// so Push can be O(n). Pop is O(1).
|
|||
/// </summary>
|
|||
[DebuggerTypeProxy(typeof(StackDebugView<>))] |
|||
[DebuggerDisplay("Count = {Count}")] |
|||
[Serializable] |
|||
public class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback |
|||
{ |
|||
[NonSerialized] |
|||
private ArrayPool<T> _pool; |
|||
[NonSerialized] |
|||
private object _syncRoot; |
|||
|
|||
private T[] _array; // Storage for stack elements. Do not rename (binary serialization)
|
|||
private int _size; // Number of items in the stack. Do not rename (binary serialization)
|
|||
private int _version; // Used to keep enumerator in sync w/ collection. Do not rename (binary serialization)
|
|||
private readonly bool _clearOnFree; |
|||
|
|||
private const int DefaultCapacity = 4; |
|||
|
|||
#region Constructors
|
|||
|
|||
/// <summary>
|
|||
/// Create a stack with the default initial capacity.
|
|||
/// </summary>
|
|||
public PooledStack() : this(ClearMode.Auto, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with the default initial capacity.
|
|||
/// </summary>
|
|||
public PooledStack(ClearMode clearMode) : this(clearMode, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with the default initial capacity.
|
|||
/// </summary>
|
|||
public PooledStack(ArrayPool<T> customPool) : this(ClearMode.Auto, customPool) { } |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with the default initial capacity and a custom ArrayPool.
|
|||
/// </summary>
|
|||
public PooledStack(ClearMode clearMode, ArrayPool<T> customPool) |
|||
{ |
|||
_pool = customPool ?? ArrayPool<T>.Shared; |
|||
_array = Array.Empty<T>(); |
|||
_clearOnFree = ShouldClear(clearMode); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with a specific initial capacity. The initial capacity
|
|||
/// must be a non-negative number.
|
|||
/// </summary>
|
|||
public PooledStack(int capacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with a specific initial capacity. The initial capacity
|
|||
/// must be a non-negative number.
|
|||
/// </summary>
|
|||
public PooledStack(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with a specific initial capacity. The initial capacity
|
|||
/// must be a non-negative number.
|
|||
/// </summary>
|
|||
public PooledStack(int capacity, ArrayPool<T> customPool) : this(capacity, ClearMode.Auto, customPool) { } |
|||
|
|||
/// <summary>
|
|||
/// Create a stack with a specific initial capacity. The initial capacity
|
|||
/// must be a non-negative number.
|
|||
/// </summary>
|
|||
public PooledStack(int capacity, ClearMode clearMode, ArrayPool<T> customPool) |
|||
{ |
|||
if (capacity < 0) |
|||
{ |
|||
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, |
|||
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); |
|||
} |
|||
_pool = customPool ?? ArrayPool<T>.Shared; |
|||
_array = _pool.Rent(capacity); |
|||
_clearOnFree = ShouldClear(clearMode); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(IEnumerable<T> enumerable) : this(enumerable, ClearMode.Auto, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode) : this(enumerable, clearMode, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(IEnumerable<T> enumerable, ArrayPool<T> customPool) : this(enumerable, ClearMode.Auto, customPool) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode, ArrayPool<T> customPool) |
|||
{ |
|||
_pool = customPool ?? ArrayPool<T>.Shared; |
|||
_clearOnFree = ShouldClear(clearMode); |
|||
|
|||
switch (enumerable) |
|||
{ |
|||
case null: |
|||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable); |
|||
break; |
|||
|
|||
case ICollection<T> collection: |
|||
if (collection.Count == 0) |
|||
{ |
|||
_array = Array.Empty<T>(); |
|||
} |
|||
else |
|||
{ |
|||
_array = _pool.Rent(collection.Count); |
|||
collection.CopyTo(_array, 0); |
|||
_size = collection.Count; |
|||
} |
|||
break; |
|||
|
|||
default: |
|||
using (var list = new PooledList<T>(enumerable)) |
|||
{ |
|||
_array = _pool.Rent(list.Count); |
|||
list.Span.CopyTo(_array); |
|||
_size = list.Count; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(T[] array, ArrayPool<T> customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(T[] array, ClearMode clearMode, ArrayPool<T> customPool) : this(array.AsSpan(), clearMode, customPool) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(ReadOnlySpan<T> span) : this(span, ClearMode.Auto, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode) : this(span, clearMode, ArrayPool<T>.Shared) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(ReadOnlySpan<T> span, ArrayPool<T> customPool) : this(span, ClearMode.Auto, customPool) { } |
|||
|
|||
/// <summary>
|
|||
/// Fills a Stack with the contents of a particular collection. The items are
|
|||
/// pushed onto the stack in the same order they are read by the enumerator.
|
|||
/// </summary>
|
|||
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode, ArrayPool<T> customPool) |
|||
{ |
|||
_pool = customPool ?? ArrayPool<T>.Shared; |
|||
_clearOnFree = ShouldClear(clearMode); |
|||
_array = _pool.Rent(span.Length); |
|||
span.CopyTo(_array); |
|||
_size = span.Length; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// The number of items in the stack.
|
|||
/// </summary>
|
|||
public int Count => _size; |
|||
|
|||
/// <summary>
|
|||
/// Returns the ClearMode behavior for the collection, denoting whether values are
|
|||
/// cleared from internal arrays before returning them to the pool.
|
|||
/// </summary>
|
|||
public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never; |
|||
|
|||
bool ICollection.IsSynchronized => false; |
|||
|
|||
object ICollection.SyncRoot |
|||
{ |
|||
get |
|||
{ |
|||
if (_syncRoot == null) |
|||
{ |
|||
Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null); |
|||
} |
|||
return _syncRoot; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes all Objects from the Stack.
|
|||
/// </summary>
|
|||
public void Clear() |
|||
{ |
|||
if (_clearOnFree) |
|||
{ |
|||
Array.Clear(_array, 0, _size); // clear the elements so that the gc can reclaim the references.
|
|||
} |
|||
_size = 0; |
|||
_version++; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares items using the default equality comparer
|
|||
/// </summary>
|
|||
public bool Contains(T item) |
|||
{ |
|||
// PERF: Internally Array.LastIndexOf calls
|
|||
// EqualityComparer<T>.Default.LastIndexOf, which
|
|||
// is specialized for different types. This
|
|||
// boosts performance since instead of making a
|
|||
// virtual method call each iteration of the loop,
|
|||
// via EqualityComparer<T>.Default.Equals, we
|
|||
// only make one virtual call to EqualityComparer.LastIndexOf.
|
|||
|
|||
return _size != 0 && Array.LastIndexOf(_array, item, _size - 1) != -1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This method removes all items which match the predicate.
|
|||
/// The complexity is O(n).
|
|||
/// </summary>
|
|||
public int RemoveWhere(Func<T, bool> match) |
|||
{ |
|||
if (match == null) |
|||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); |
|||
|
|||
int freeIndex = 0; // the first free slot in items array
|
|||
|
|||
// Find the first item which needs to be removed.
|
|||
while (freeIndex < _size && !match(_array[freeIndex])) |
|||
freeIndex++; |
|||
if (freeIndex >= _size) |
|||
return 0; |
|||
|
|||
int current = freeIndex + 1; |
|||
while (current < _size) |
|||
{ |
|||
// Find the first item which needs to be kept.
|
|||
while (current < _size && match(_array[current])) |
|||
current++; |
|||
|
|||
if (current < _size) |
|||
{ |
|||
// copy item to the free slot.
|
|||
_array[freeIndex++] = _array[current++]; |
|||
} |
|||
} |
|||
|
|||
if (_clearOnFree) |
|||
{ |
|||
// Clear the removed elements so that the gc can reclaim the references.
|
|||
Array.Clear(_array, freeIndex, _size - freeIndex); |
|||
} |
|||
|
|||
int result = _size - freeIndex; |
|||
_size = freeIndex; |
|||
_version++; |
|||
return result; |
|||
} |
|||
|
|||
// Copies the stack into an array.
|
|||
public void CopyTo(T[] array, int arrayIndex) |
|||
{ |
|||
if (array == null) |
|||
{ |
|||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); |
|||
} |
|||
|
|||
if (arrayIndex < 0 || arrayIndex > array.Length) |
|||
{ |
|||
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex); |
|||
} |
|||
|
|||
if (array.Length - arrayIndex < _size) |
|||
{ |
|||
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); |
|||
} |
|||
|
|||
Debug.Assert(array != _array); |
|||
int srcIndex = 0; |
|||
int dstIndex = arrayIndex + _size; |
|||
while (srcIndex < _size) |
|||
{ |
|||
array[--dstIndex] = _array[srcIndex++]; |
|||
} |
|||
} |
|||
|
|||
public void CopyTo(Span<T> span) |
|||
{ |
|||
if (span.Length < _size) |
|||
{ |
|||
ThrowHelper.ThrowArgumentException_DestinationTooShort(); |
|||
} |
|||
|
|||
int srcIndex = 0; |
|||
int dstIndex = _size; |
|||
while (srcIndex < _size) |
|||
{ |
|||
span[--dstIndex] = _array[srcIndex++]; |
|||
} |
|||
} |
|||
|
|||
void ICollection.CopyTo(Array array, int arrayIndex) |
|||
{ |
|||
if (array == null) |
|||
{ |
|||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); |
|||
} |
|||
|
|||
if (array.Rank != 1) |
|||
{ |
|||
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); |
|||
} |
|||
|
|||
if (array.GetLowerBound(0) != 0) |
|||
{ |
|||
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound, ExceptionArgument.array); |
|||
} |
|||
|
|||
if (arrayIndex < 0 || arrayIndex > array.Length) |
|||
{ |
|||
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex); |
|||
} |
|||
|
|||
if (array.Length - arrayIndex < _size) |
|||
{ |
|||
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
Array.Copy(_array, 0, array, arrayIndex, _size); |
|||
Array.Reverse(array, arrayIndex, _size); |
|||
} |
|||
catch (ArrayTypeMismatchException) |
|||
{ |
|||
ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns an IEnumerator for this PooledStack.
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public Enumerator GetEnumerator() |
|||
=> new Enumerator(this); |
|||
|
|||
/// <internalonly/>
|
|||
IEnumerator<T> IEnumerable<T>.GetEnumerator() |
|||
=> new Enumerator(this); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
=> new Enumerator(this); |
|||
|
|||
public void TrimExcess() |
|||
{ |
|||
if (_size == 0) |
|||
{ |
|||
ReturnArray(replaceWith: Array.Empty<T>()); |
|||
_version++; |
|||
return; |
|||
} |
|||
|
|||
int threshold = (int)(_array.Length * 0.9); |
|||
if (_size < threshold) |
|||
{ |
|||
var newArray = _pool.Rent(_size); |
|||
if (newArray.Length < _array.Length) |
|||
{ |
|||
Array.Copy(_array, newArray, _size); |
|||
ReturnArray(replaceWith: newArray); |
|||
_version++; |
|||
} |
|||
else |
|||
{ |
|||
// The array from the pool wasn't any smaller than the one we already had,
|
|||
// (we can only control minimum size) so return it and do nothing.
|
|||
// If we create an exact-sized array not from the pool, we'll
|
|||
// get an exception when returning it to the pool.
|
|||
_pool.Return(newArray); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the top object on the stack without removing it. If the stack
|
|||
/// is empty, Peek throws an InvalidOperationException.
|
|||
/// </summary>
|
|||
public T Peek() |
|||
{ |
|||
int size = _size - 1; |
|||
T[] array = _array; |
|||
|
|||
if ((uint)size >= (uint)array.Length) |
|||
{ |
|||
ThrowForEmptyStack(); |
|||
} |
|||
|
|||
return array[size]; |
|||
} |
|||
|
|||
public bool TryPeek(out T result) |
|||
{ |
|||
int size = _size - 1; |
|||
T[] array = _array; |
|||
|
|||
if ((uint)size >= (uint)array.Length) |
|||
{ |
|||
result = default; |
|||
return false; |
|||
} |
|||
result = array[size]; |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Pops an item from the top of the stack. If the stack is empty, Pop
|
|||
/// throws an InvalidOperationException.
|
|||
/// </summary>
|
|||
public T Pop() |
|||
{ |
|||
int size = _size - 1; |
|||
T[] array = _array; |
|||
|
|||
// if (_size == 0) is equivalent to if (size == -1), and this case
|
|||
// is covered with (uint)size, thus allowing bounds check elimination
|
|||
// https://github.com/dotnet/coreclr/pull/9773
|
|||
if ((uint)size >= (uint)array.Length) |
|||
{ |
|||
ThrowForEmptyStack(); |
|||
} |
|||
|
|||
_version++; |
|||
_size = size; |
|||
T item = array[size]; |
|||
if (_clearOnFree) |
|||
{ |
|||
array[size] = default; // Free memory quicker.
|
|||
} |
|||
return item; |
|||
} |
|||
|
|||
public bool TryPop(out T result) |
|||
{ |
|||
int size = _size - 1; |
|||
T[] array = _array; |
|||
|
|||
if ((uint)size >= (uint)array.Length) |
|||
{ |
|||
result = default; |
|||
return false; |
|||
} |
|||
|
|||
_version++; |
|||
_size = size; |
|||
result = array[size]; |
|||
if (_clearOnFree) |
|||
{ |
|||
array[size] = default; // Free memory quicker.
|
|||
} |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Pushes an item to the top of the stack.
|
|||
/// </summary>
|
|||
public void Push(T item) |
|||
{ |
|||
int size = _size; |
|||
T[] array = _array; |
|||
|
|||
if ((uint)size < (uint)array.Length) |
|||
{ |
|||
array[size] = item; |
|||
_version++; |
|||
_size = size + 1; |
|||
} |
|||
else |
|||
{ |
|||
PushWithResize(item); |
|||
} |
|||
} |
|||
|
|||
// Non-inline from Stack.Push to improve its code quality as uncommon path
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private void PushWithResize(T item) |
|||
{ |
|||
var newArray = _pool.Rent((_array.Length == 0) ? DefaultCapacity : 2 * _array.Length); |
|||
Array.Copy(_array, newArray, _size); |
|||
ReturnArray(replaceWith: newArray); |
|||
_array[_size] = item; |
|||
_version++; |
|||
_size++; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies the Stack to an array, in the same order Pop would return the items.
|
|||
/// </summary>
|
|||
public T[] ToArray() |
|||
{ |
|||
if (_size == 0) |
|||
return Array.Empty<T>(); |
|||
|
|||
T[] objArray = new T[_size]; |
|||
int i = 0; |
|||
while (i < _size) |
|||
{ |
|||
objArray[i] = _array[_size - i - 1]; |
|||
i++; |
|||
} |
|||
return objArray; |
|||
} |
|||
|
|||
private void ThrowForEmptyStack() |
|||
{ |
|||
Debug.Assert(_size == 0); |
|||
throw new InvalidOperationException("Stack was empty."); |
|||
} |
|||
|
|||
private void ReturnArray(T[] replaceWith = null) |
|||
{ |
|||
if (_array?.Length > 0) |
|||
{ |
|||
try |
|||
{ |
|||
_pool.Return(_array, clearArray: _clearOnFree); |
|||
} |
|||
catch (ArgumentException) |
|||
{ |
|||
// oh well, the array pool didn't like our array
|
|||
} |
|||
} |
|||
|
|||
if (!(replaceWith is null)) |
|||
{ |
|||
_array = replaceWith; |
|||
} |
|||
} |
|||
|
|||
private static bool ShouldClear(ClearMode mode) |
|||
{ |
|||
#if NETCOREAPP2_1
|
|||
return mode == ClearMode.Always |
|||
|| (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>()); |
|||
#else
|
|||
return mode != ClearMode.Never; |
|||
#endif
|
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
ReturnArray(replaceWith: Array.Empty<T>()); |
|||
_size = 0; |
|||
_version++; |
|||
} |
|||
|
|||
void IDeserializationCallback.OnDeserialization(object sender) |
|||
{ |
|||
// We can't serialize array pools, so deserialized PooledStacks will
|
|||
// have to use the shared pool, even if they were using a custom pool
|
|||
// before serialization.
|
|||
_pool = ArrayPool<T>.Shared; |
|||
} |
|||
|
|||
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] |
|||
public struct Enumerator : IEnumerator<T>, IEnumerator |
|||
{ |
|||
private readonly PooledStack<T> _stack; |
|||
private readonly int _version; |
|||
private int _index; |
|||
private T _currentElement; |
|||
|
|||
internal Enumerator(PooledStack<T> stack) |
|||
{ |
|||
_stack = stack; |
|||
_version = stack._version; |
|||
_index = -2; |
|||
_currentElement = default; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_index = -1; |
|||
} |
|||
|
|||
public bool MoveNext() |
|||
{ |
|||
bool retval; |
|||
if (_version != _stack._version) |
|||
throw new InvalidOperationException("Collection was modified during enumeration."); |
|||
if (_index == -2) |
|||
{ // First call to enumerator.
|
|||
_index = _stack._size - 1; |
|||
retval = (_index >= 0); |
|||
if (retval) |
|||
_currentElement = _stack._array[_index]; |
|||
return retval; |
|||
} |
|||
if (_index == -1) |
|||
{ // End of enumeration.
|
|||
return false; |
|||
} |
|||
|
|||
retval = (--_index >= 0); |
|||
if (retval) |
|||
_currentElement = _stack._array[_index]; |
|||
else |
|||
_currentElement = default; |
|||
return retval; |
|||
} |
|||
|
|||
public T Current |
|||
{ |
|||
get |
|||
{ |
|||
if (_index < 0) |
|||
ThrowEnumerationNotStartedOrEnded(); |
|||
return _currentElement; |
|||
} |
|||
} |
|||
|
|||
private void ThrowEnumerationNotStartedOrEnded() |
|||
{ |
|||
Debug.Assert(_index == -1 || _index == -2); |
|||
throw new InvalidOperationException(_index == -2 ? "Enumeration was not started." : "Enumeration has ended."); |
|||
} |
|||
|
|||
object IEnumerator.Current |
|||
{ |
|||
get { return Current; } |
|||
} |
|||
|
|||
void IEnumerator.Reset() |
|||
{ |
|||
if (_version != _stack._version) |
|||
throw new InvalidOperationException("Collection was modified during enumeration."); |
|||
_index = -2; |
|||
_currentElement = default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
|
|||
namespace Avalonia.Collections.Pooled |
|||
{ |
|||
internal sealed class StackDebugView<T> |
|||
{ |
|||
private readonly PooledStack<T> _stack; |
|||
|
|||
public StackDebugView(PooledStack<T> stack) |
|||
{ |
|||
_stack = stack ?? throw new ArgumentNullException(nameof(stack)); |
|||
} |
|||
|
|||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
|||
public T[] Items |
|||
{ |
|||
get |
|||
{ |
|||
return _stack.ToArray(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,691 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
|
|||
// This file defines an internal class used to throw exceptions in BCL code.
|
|||
// The main purpose is to reduce code size.
|
|||
//
|
|||
// The old way to throw an exception generates quite a lot IL code and assembly code.
|
|||
// Following is an example:
|
|||
// C# source
|
|||
// throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
|
|||
// IL code:
|
|||
// IL_0003: ldstr "key"
|
|||
// IL_0008: ldstr "ArgumentNull_Key"
|
|||
// IL_000d: call string System.Environment::GetResourceString(string)
|
|||
// IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string)
|
|||
// IL_0017: throw
|
|||
// which is 21bytes in IL.
|
|||
//
|
|||
// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
|
|||
// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
|
|||
// argument name and resource name in a small integer. The source code will be changed to
|
|||
// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
|
|||
//
|
|||
// The IL code will be 7 bytes.
|
|||
// IL_0008: ldc.i4.4
|
|||
// IL_0009: ldc.i4.4
|
|||
// IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
|
|||
// IL_000f: ldarg.0
|
|||
//
|
|||
// This will also reduce the Jitted code size a lot.
|
|||
//
|
|||
// It is very important we do this for generic classes because we can easily generate the same code
|
|||
// multiple times for different instantiation.
|
|||
//
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Serialization; |
|||
|
|||
namespace Avalonia.Collections.Pooled |
|||
{ |
|||
internal static class ThrowHelper |
|||
{ |
|||
internal static void ThrowArrayTypeMismatchException() |
|||
{ |
|||
throw new ArrayTypeMismatchException(); |
|||
} |
|||
|
|||
internal static void ThrowIndexOutOfRangeException() |
|||
{ |
|||
throw new IndexOutOfRangeException(); |
|||
} |
|||
|
|||
internal static void ThrowArgumentOutOfRangeException() |
|||
{ |
|||
throw new ArgumentOutOfRangeException(); |
|||
} |
|||
|
|||
internal static void ThrowArgumentException_DestinationTooShort() |
|||
{ |
|||
throw new ArgumentException("Destination too short."); |
|||
} |
|||
|
|||
internal static void ThrowArgumentException_OverlapAlignmentMismatch() |
|||
{ |
|||
throw new ArgumentException("Overlap alignment mismatch."); |
|||
} |
|||
|
|||
internal static void ThrowArgumentOutOfRange_IndexException() |
|||
{ |
|||
throw GetArgumentOutOfRangeException(ExceptionArgument.index, |
|||
ExceptionResource.ArgumentOutOfRange_Index); |
|||
} |
|||
|
|||
internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException() |
|||
{ |
|||
throw GetArgumentOutOfRangeException(ExceptionArgument.index, |
|||
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); |
|||
} |
|||
|
|||
internal static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() |
|||
{ |
|||
throw GetArgumentOutOfRangeException(ExceptionArgument.value, |
|||
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); |
|||
} |
|||
|
|||
internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum() |
|||
{ |
|||
throw GetArgumentOutOfRangeException(ExceptionArgument.length, |
|||
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); |
|||
} |
|||
|
|||
internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index() |
|||
{ |
|||
throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex, |
|||
ExceptionResource.ArgumentOutOfRange_Index); |
|||
} |
|||
|
|||
internal static void ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count() |
|||
{ |
|||
throw GetArgumentOutOfRangeException(ExceptionArgument.count, |
|||
ExceptionResource.ArgumentOutOfRange_Count); |
|||
} |
|||
|
|||
internal static void ThrowWrongKeyTypeArgumentException<T>(T key, Type targetType) |
|||
{ |
|||
// Generic key to move the boxing to the right hand side of throw
|
|||
throw GetWrongKeyTypeArgumentException((object)key, targetType); |
|||
} |
|||
|
|||
internal static void ThrowWrongValueTypeArgumentException<T>(T value, Type targetType) |
|||
{ |
|||
// Generic key to move the boxing to the right hand side of throw
|
|||
throw GetWrongValueTypeArgumentException((object)value, targetType); |
|||
} |
|||
|
|||
private static ArgumentException GetAddingDuplicateWithKeyArgumentException(object key) |
|||
{ |
|||
return new ArgumentException($"Error adding duplicate with key: {key}."); |
|||
} |
|||
|
|||
internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key) |
|||
{ |
|||
// Generic key to move the boxing to the right hand side of throw
|
|||
throw GetAddingDuplicateWithKeyArgumentException((object)key); |
|||
} |
|||
|
|||
internal static void ThrowKeyNotFoundException<T>(T key) |
|||
{ |
|||
// Generic key to move the boxing to the right hand side of throw
|
|||
throw GetKeyNotFoundException((object)key); |
|||
} |
|||
|
|||
internal static void ThrowArgumentException(ExceptionResource resource) |
|||
{ |
|||
throw GetArgumentException(resource); |
|||
} |
|||
|
|||
internal static void ThrowArgumentException(ExceptionResource resource, ExceptionArgument argument) |
|||
{ |
|||
throw GetArgumentException(resource, argument); |
|||
} |
|||
|
|||
private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument) |
|||
{ |
|||
return new ArgumentNullException(GetArgumentName(argument)); |
|||
} |
|||
|
|||
internal static void ThrowArgumentNullException(ExceptionArgument argument) |
|||
{ |
|||
throw GetArgumentNullException(argument); |
|||
} |
|||
|
|||
internal static void ThrowArgumentNullException(ExceptionResource resource) |
|||
{ |
|||
throw new ArgumentNullException(GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowArgumentNullException(ExceptionArgument argument, ExceptionResource resource) |
|||
{ |
|||
throw new ArgumentNullException(GetArgumentName(argument), GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(GetArgumentName(argument)); |
|||
} |
|||
|
|||
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) |
|||
{ |
|||
throw GetArgumentOutOfRangeException(argument, resource); |
|||
} |
|||
|
|||
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource) |
|||
{ |
|||
throw GetArgumentOutOfRangeException(argument, paramNumber, resource); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException(ExceptionResource resource) |
|||
{ |
|||
throw GetInvalidOperationException(resource); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException(ExceptionResource resource, Exception e) |
|||
{ |
|||
throw new InvalidOperationException(GetResourceString(resource), e); |
|||
} |
|||
|
|||
internal static void ThrowSerializationException(ExceptionResource resource) |
|||
{ |
|||
throw new SerializationException(GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowSecurityException(ExceptionResource resource) |
|||
{ |
|||
throw new System.Security.SecurityException(GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowRankException(ExceptionResource resource) |
|||
{ |
|||
throw new RankException(GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowNotSupportedException(ExceptionResource resource) |
|||
{ |
|||
throw new NotSupportedException(GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowUnauthorizedAccessException(ExceptionResource resource) |
|||
{ |
|||
throw new UnauthorizedAccessException(GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowObjectDisposedException(string objectName, ExceptionResource resource) |
|||
{ |
|||
throw new ObjectDisposedException(objectName, GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowObjectDisposedException(ExceptionResource resource) |
|||
{ |
|||
throw new ObjectDisposedException(null, GetResourceString(resource)); |
|||
} |
|||
|
|||
internal static void ThrowNotSupportedException() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
internal static void ThrowAggregateException(List<Exception> exceptions) |
|||
{ |
|||
throw new AggregateException(exceptions); |
|||
} |
|||
|
|||
internal static void ThrowOutOfMemoryException() |
|||
{ |
|||
throw new OutOfMemoryException(); |
|||
} |
|||
|
|||
internal static void ThrowArgumentException_Argument_InvalidArrayType() |
|||
{ |
|||
throw new ArgumentException("Invalid array type."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumNotStarted() |
|||
{ |
|||
throw new InvalidOperationException("Enumeration has not started."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumEnded() |
|||
{ |
|||
throw new InvalidOperationException("Enumeration has ended."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_EnumCurrent(int index) |
|||
{ |
|||
throw GetInvalidOperationException_EnumCurrent(index); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() |
|||
{ |
|||
throw new InvalidOperationException("Collection was modified during enumeration."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen() |
|||
{ |
|||
throw new InvalidOperationException("Invalid enumerator state: enumeration cannot proceed."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_InvalidOperation_NoValue() |
|||
{ |
|||
throw new InvalidOperationException("No value provided."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported() |
|||
{ |
|||
throw new InvalidOperationException("Concurrent operations are not supported."); |
|||
} |
|||
|
|||
internal static void ThrowInvalidOperationException_HandleIsNotInitialized() |
|||
{ |
|||
throw new InvalidOperationException("Handle is not initialized."); |
|||
} |
|||
|
|||
internal static void ThrowFormatException_BadFormatSpecifier() |
|||
{ |
|||
throw new FormatException("Bad format specifier."); |
|||
} |
|||
|
|||
private static ArgumentException GetArgumentException(ExceptionResource resource) |
|||
{ |
|||
return new ArgumentException(GetResourceString(resource)); |
|||
} |
|||
|
|||
private static InvalidOperationException GetInvalidOperationException(ExceptionResource resource) |
|||
{ |
|||
return new InvalidOperationException(GetResourceString(resource)); |
|||
} |
|||
|
|||
private static ArgumentException GetWrongKeyTypeArgumentException(object key, Type targetType) |
|||
{ |
|||
return new ArgumentException($"Wrong key type. Expected {targetType}, got: '{key}'.", nameof(key)); |
|||
} |
|||
|
|||
private static ArgumentException GetWrongValueTypeArgumentException(object value, Type targetType) |
|||
{ |
|||
return new ArgumentException($"Wrong value type. Expected {targetType}, got: '{value}'.", nameof(value)); |
|||
} |
|||
|
|||
private static KeyNotFoundException GetKeyNotFoundException(object key) |
|||
{ |
|||
return new KeyNotFoundException($"Key not found: {key}"); |
|||
} |
|||
|
|||
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) |
|||
{ |
|||
return new ArgumentOutOfRangeException(GetArgumentName(argument), GetResourceString(resource)); |
|||
} |
|||
|
|||
private static ArgumentException GetArgumentException(ExceptionResource resource, ExceptionArgument argument) |
|||
{ |
|||
return new ArgumentException(GetResourceString(resource), GetArgumentName(argument)); |
|||
} |
|||
|
|||
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource) |
|||
{ |
|||
return new ArgumentOutOfRangeException(GetArgumentName(argument) + "[" + paramNumber.ToString() + "]", GetResourceString(resource)); |
|||
} |
|||
|
|||
private static InvalidOperationException GetInvalidOperationException_EnumCurrent(int index) |
|||
{ |
|||
return new InvalidOperationException( |
|||
index < 0 ? |
|||
"Enumeration has not started" : |
|||
"Enumeration has ended"); |
|||
} |
|||
|
|||
// Allow nulls for reference types and Nullable<U>, but not for value types.
|
|||
// Aggressively inline so the jit evaluates the if in place and either drops the call altogether
|
|||
// Or just leaves null test and call to the Non-returning ThrowHelper.ThrowArgumentNullException
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void IfNullAndNullsAreIllegalThenThrow<T>(object value, ExceptionArgument argName) |
|||
{ |
|||
// Note that default(T) is not equal to null for value types except when T is Nullable<U>.
|
|||
if (!(default(T) == null) && value == null) |
|||
ThrowHelper.ThrowArgumentNullException(argName); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal static void ThrowForUnsupportedVectorBaseType<T>() where T : struct |
|||
{ |
|||
if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) && |
|||
typeof(T) != typeof(short) && typeof(T) != typeof(ushort) && |
|||
typeof(T) != typeof(int) && typeof(T) != typeof(uint) && |
|||
typeof(T) != typeof(long) && typeof(T) != typeof(ulong) && |
|||
typeof(T) != typeof(float) && typeof(T) != typeof(double)) |
|||
{ |
|||
ThrowNotSupportedException(ExceptionResource.Arg_TypeNotSupported); |
|||
} |
|||
} |
|||
|
|||
#if false // Reflection-based implementation does not work for CoreRT/ProjectN
|
|||
// This function will convert an ExceptionArgument enum value to the argument name string.
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private static string GetArgumentName(ExceptionArgument argument) |
|||
{ |
|||
Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument), |
|||
"The enum value is not defined, please check the ExceptionArgument Enum."); |
|||
|
|||
return argument.ToString(); |
|||
} |
|||
#endif
|
|||
|
|||
private static string GetArgumentName(ExceptionArgument argument) |
|||
{ |
|||
switch (argument) |
|||
{ |
|||
case ExceptionArgument.obj: |
|||
return "obj"; |
|||
case ExceptionArgument.dictionary: |
|||
return "dictionary"; |
|||
case ExceptionArgument.array: |
|||
return "array"; |
|||
case ExceptionArgument.info: |
|||
return "info"; |
|||
case ExceptionArgument.key: |
|||
return "key"; |
|||
case ExceptionArgument.text: |
|||
return "text"; |
|||
case ExceptionArgument.values: |
|||
return "values"; |
|||
case ExceptionArgument.value: |
|||
return "value"; |
|||
case ExceptionArgument.startIndex: |
|||
return "startIndex"; |
|||
case ExceptionArgument.task: |
|||
return "task"; |
|||
case ExceptionArgument.ch: |
|||
return "ch"; |
|||
case ExceptionArgument.s: |
|||
return "s"; |
|||
case ExceptionArgument.input: |
|||
return "input"; |
|||
case ExceptionArgument.list: |
|||
return "list"; |
|||
case ExceptionArgument.index: |
|||
return "index"; |
|||
case ExceptionArgument.capacity: |
|||
return "capacity"; |
|||
case ExceptionArgument.collection: |
|||
return "collection"; |
|||
case ExceptionArgument.item: |
|||
return "item"; |
|||
case ExceptionArgument.converter: |
|||
return "converter"; |
|||
case ExceptionArgument.match: |
|||
return "match"; |
|||
case ExceptionArgument.count: |
|||
return "count"; |
|||
case ExceptionArgument.action: |
|||
return "action"; |
|||
case ExceptionArgument.comparison: |
|||
return "comparison"; |
|||
case ExceptionArgument.exceptions: |
|||
return "exceptions"; |
|||
case ExceptionArgument.exception: |
|||
return "exception"; |
|||
case ExceptionArgument.enumerable: |
|||
return "enumerable"; |
|||
case ExceptionArgument.start: |
|||
return "start"; |
|||
case ExceptionArgument.format: |
|||
return "format"; |
|||
case ExceptionArgument.culture: |
|||
return "culture"; |
|||
case ExceptionArgument.comparer: |
|||
return "comparer"; |
|||
case ExceptionArgument.comparable: |
|||
return "comparable"; |
|||
case ExceptionArgument.source: |
|||
return "source"; |
|||
case ExceptionArgument.state: |
|||
return "state"; |
|||
case ExceptionArgument.length: |
|||
return "length"; |
|||
case ExceptionArgument.comparisonType: |
|||
return "comparisonType"; |
|||
case ExceptionArgument.manager: |
|||
return "manager"; |
|||
case ExceptionArgument.sourceBytesToCopy: |
|||
return "sourceBytesToCopy"; |
|||
case ExceptionArgument.callBack: |
|||
return "callBack"; |
|||
case ExceptionArgument.creationOptions: |
|||
return "creationOptions"; |
|||
case ExceptionArgument.function: |
|||
return "function"; |
|||
case ExceptionArgument.delay: |
|||
return "delay"; |
|||
case ExceptionArgument.millisecondsDelay: |
|||
return "millisecondsDelay"; |
|||
case ExceptionArgument.millisecondsTimeout: |
|||
return "millisecondsTimeout"; |
|||
case ExceptionArgument.timeout: |
|||
return "timeout"; |
|||
case ExceptionArgument.type: |
|||
return "type"; |
|||
case ExceptionArgument.sourceIndex: |
|||
return "sourceIndex"; |
|||
case ExceptionArgument.sourceArray: |
|||
return "sourceArray"; |
|||
case ExceptionArgument.destinationIndex: |
|||
return "destinationIndex"; |
|||
case ExceptionArgument.destinationArray: |
|||
return "destinationArray"; |
|||
case ExceptionArgument.other: |
|||
return "other"; |
|||
case ExceptionArgument.newSize: |
|||
return "newSize"; |
|||
case ExceptionArgument.lowerBounds: |
|||
return "lowerBounds"; |
|||
case ExceptionArgument.lengths: |
|||
return "lengths"; |
|||
case ExceptionArgument.len: |
|||
return "len"; |
|||
case ExceptionArgument.keys: |
|||
return "keys"; |
|||
case ExceptionArgument.indices: |
|||
return "indices"; |
|||
case ExceptionArgument.endIndex: |
|||
return "endIndex"; |
|||
case ExceptionArgument.elementType: |
|||
return "elementType"; |
|||
case ExceptionArgument.arrayIndex: |
|||
return "arrayIndex"; |
|||
default: |
|||
Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum."); |
|||
return argument.ToString(); |
|||
} |
|||
} |
|||
|
|||
#if false // Reflection-based implementation does not work for CoreRT/ProjectN
|
|||
// This function will convert an ExceptionResource enum value to the resource string.
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private static string GetResourceString(ExceptionResource resource) |
|||
{ |
|||
Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource), |
|||
"The enum value is not defined, please check the ExceptionResource Enum."); |
|||
|
|||
return SR.GetResourceString(resource.ToString()); |
|||
} |
|||
#endif
|
|||
|
|||
private static string GetResourceString(ExceptionResource resource) |
|||
{ |
|||
switch (resource) |
|||
{ |
|||
case ExceptionResource.ArgumentOutOfRange_Index: |
|||
return "Argument 'index' was out of the range of valid values."; |
|||
case ExceptionResource.ArgumentOutOfRange_Count: |
|||
return "Argument 'count' was out of the range of valid values."; |
|||
case ExceptionResource.Arg_ArrayPlusOffTooSmall: |
|||
return "Array plus offset too small."; |
|||
case ExceptionResource.NotSupported_ReadOnlyCollection: |
|||
return "This operation is not supported on a read-only collection."; |
|||
case ExceptionResource.Arg_RankMultiDimNotSupported: |
|||
return "Multi-dimensional arrays are not supported."; |
|||
case ExceptionResource.Arg_NonZeroLowerBound: |
|||
return "Arrays with a non-zero lower bound are not supported."; |
|||
case ExceptionResource.ArgumentOutOfRange_ListInsert: |
|||
return "Insertion index was out of the range of valid values."; |
|||
case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum: |
|||
return "The number must be non-negative."; |
|||
case ExceptionResource.ArgumentOutOfRange_SmallCapacity: |
|||
return "The capacity cannot be set below the current Count."; |
|||
case ExceptionResource.Argument_InvalidOffLen: |
|||
return "Invalid offset length."; |
|||
case ExceptionResource.ArgumentOutOfRange_BiggerThanCollection: |
|||
return "The given value was larger than the size of the collection."; |
|||
case ExceptionResource.Serialization_MissingKeys: |
|||
return "Serialization error: missing keys."; |
|||
case ExceptionResource.Serialization_NullKey: |
|||
return "Serialization error: null key."; |
|||
case ExceptionResource.NotSupported_KeyCollectionSet: |
|||
return "The KeyCollection does not support modification."; |
|||
case ExceptionResource.NotSupported_ValueCollectionSet: |
|||
return "The ValueCollection does not support modification."; |
|||
case ExceptionResource.InvalidOperation_NullArray: |
|||
return "Null arrays are not supported."; |
|||
case ExceptionResource.InvalidOperation_HSCapacityOverflow: |
|||
return "Set hash capacity overflow. Cannot increase size."; |
|||
case ExceptionResource.NotSupported_StringComparison: |
|||
return "String comparison not supported."; |
|||
case ExceptionResource.ConcurrentCollection_SyncRoot_NotSupported: |
|||
return "SyncRoot not supported."; |
|||
case ExceptionResource.ArgumentException_OtherNotArrayOfCorrectLength: |
|||
return "The other array is not of the correct length."; |
|||
case ExceptionResource.ArgumentOutOfRange_EndIndexStartIndex: |
|||
return "The end index does not come after the start index."; |
|||
case ExceptionResource.ArgumentOutOfRange_HugeArrayNotSupported: |
|||
return "Huge arrays are not supported."; |
|||
case ExceptionResource.Argument_AddingDuplicate: |
|||
return "Duplicate item added."; |
|||
case ExceptionResource.Argument_InvalidArgumentForComparison: |
|||
return "Invalid argument for comparison."; |
|||
case ExceptionResource.Arg_LowerBoundsMustMatch: |
|||
return "Array lower bounds must match."; |
|||
case ExceptionResource.Arg_MustBeType: |
|||
return "Argument must be of type: "; |
|||
case ExceptionResource.InvalidOperation_IComparerFailed: |
|||
return "IComparer failed."; |
|||
case ExceptionResource.NotSupported_FixedSizeCollection: |
|||
return "This operation is not suppored on a fixed-size collection."; |
|||
case ExceptionResource.Rank_MultiDimNotSupported: |
|||
return "Multi-dimensional arrays are not supported."; |
|||
case ExceptionResource.Arg_TypeNotSupported: |
|||
return "Type not supported."; |
|||
default: |
|||
Debug.Assert(false, |
|||
"The enum value is not defined, please check the ExceptionResource Enum."); |
|||
return resource.ToString(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//
|
|||
// The convention for this enum is using the argument name as the enum name
|
|||
//
|
|||
internal enum ExceptionArgument |
|||
{ |
|||
obj, |
|||
dictionary, |
|||
array, |
|||
info, |
|||
key, |
|||
text, |
|||
values, |
|||
value, |
|||
startIndex, |
|||
task, |
|||
ch, |
|||
s, |
|||
input, |
|||
list, |
|||
index, |
|||
capacity, |
|||
collection, |
|||
item, |
|||
converter, |
|||
match, |
|||
count, |
|||
action, |
|||
comparison, |
|||
exceptions, |
|||
exception, |
|||
enumerable, |
|||
start, |
|||
format, |
|||
culture, |
|||
comparer, |
|||
comparable, |
|||
source, |
|||
state, |
|||
length, |
|||
comparisonType, |
|||
manager, |
|||
sourceBytesToCopy, |
|||
callBack, |
|||
creationOptions, |
|||
function, |
|||
delay, |
|||
millisecondsDelay, |
|||
millisecondsTimeout, |
|||
timeout, |
|||
type, |
|||
sourceIndex, |
|||
sourceArray, |
|||
destinationIndex, |
|||
destinationArray, |
|||
other, |
|||
newSize, |
|||
lowerBounds, |
|||
lengths, |
|||
len, |
|||
keys, |
|||
indices, |
|||
endIndex, |
|||
elementType, |
|||
arrayIndex |
|||
} |
|||
|
|||
//
|
|||
// The convention for this enum is using the resource name as the enum name
|
|||
//
|
|||
internal enum ExceptionResource |
|||
{ |
|||
ArgumentOutOfRange_Index, |
|||
ArgumentOutOfRange_Count, |
|||
Arg_ArrayPlusOffTooSmall, |
|||
NotSupported_ReadOnlyCollection, |
|||
Arg_RankMultiDimNotSupported, |
|||
Arg_NonZeroLowerBound, |
|||
ArgumentOutOfRange_ListInsert, |
|||
ArgumentOutOfRange_NeedNonNegNum, |
|||
ArgumentOutOfRange_SmallCapacity, |
|||
Argument_InvalidOffLen, |
|||
ArgumentOutOfRange_BiggerThanCollection, |
|||
Serialization_MissingKeys, |
|||
Serialization_NullKey, |
|||
NotSupported_KeyCollectionSet, |
|||
NotSupported_ValueCollectionSet, |
|||
InvalidOperation_NullArray, |
|||
InvalidOperation_HSCapacityOverflow, |
|||
NotSupported_StringComparison, |
|||
ConcurrentCollection_SyncRoot_NotSupported, |
|||
ArgumentException_OtherNotArrayOfCorrectLength, |
|||
ArgumentOutOfRange_EndIndexStartIndex, |
|||
ArgumentOutOfRange_HugeArrayNotSupported, |
|||
Argument_AddingDuplicate, |
|||
Argument_InvalidArgumentForComparison, |
|||
Arg_LowerBoundsMustMatch, |
|||
Arg_MustBeType, |
|||
InvalidOperation_IComparerFailed, |
|||
NotSupported_FixedSizeCollection, |
|||
Rank_MultiDimNotSupported, |
|||
Arg_TypeNotSupported, |
|||
} |
|||
} |
|||
@ -0,0 +1,432 @@ |
|||
using System; |
|||
using Avalonia.Utilities; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// Describes the type of a <see cref="BindingValue{T}"/>.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum BindingValueType |
|||
{ |
|||
/// <summary>
|
|||
/// An unset value: the target property will revert to its unbound state until a new
|
|||
/// binding value is produced.
|
|||
/// </summary>
|
|||
UnsetValue = 0, |
|||
|
|||
/// <summary>
|
|||
/// Do nothing: the binding value will be ignored.
|
|||
/// </summary>
|
|||
DoNothing = 1, |
|||
|
|||
/// <summary>
|
|||
/// A simple value.
|
|||
/// </summary>
|
|||
Value = 2 | HasValue, |
|||
|
|||
/// <summary>
|
|||
/// A binding error, such as a missing source property.
|
|||
/// </summary>
|
|||
BindingError = 3 | HasError, |
|||
|
|||
/// <summary>
|
|||
/// A data validation error.
|
|||
/// </summary>
|
|||
DataValidationError = 4 | HasError, |
|||
|
|||
/// <summary>
|
|||
/// A binding error with a fallback value.
|
|||
/// </summary>
|
|||
BindingErrorWithFallback = BindingError | HasValue, |
|||
|
|||
/// <summary>
|
|||
/// A data validation error with a fallback value.
|
|||
/// </summary>
|
|||
DataValidationErrorWithFallback = DataValidationError | HasValue, |
|||
|
|||
TypeMask = 0x00ff, |
|||
HasValue = 0x0100, |
|||
HasError = 0x0200, |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A value passed into a binding.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type.</typeparam>
|
|||
/// <remarks>
|
|||
/// The avalonia binding system is typed, and as such additional state is stored in this
|
|||
/// structure. A binding value can be in a number of states, described by the
|
|||
/// <see cref="Type"/> property:
|
|||
///
|
|||
/// - <see cref="BindingValueType.Value"/>: a simple value
|
|||
/// - <see cref="BindingValueType.UnsetValue"/>: the target property will revert to its unbound
|
|||
/// state until a new binding value is produced. Represented by
|
|||
/// <see cref="AvaloniaProperty.UnsetValue"/> in an untyped context
|
|||
/// - <see cref="BindingValueType.DoNothing"/>: the binding value will be ignored. Represented
|
|||
/// by <see cref="BindingOperations.DoNothing"/> in an untyped context
|
|||
/// - <see cref="BindingValueType.BindingError"/>: a binding error, such as a missing source
|
|||
/// property, with an optional fallback value
|
|||
/// - <see cref="BindingValueType.DataValidationError"/>: a data validation error, with an
|
|||
/// optional fallback value
|
|||
///
|
|||
/// To create a new binding value you can:
|
|||
///
|
|||
/// - For a simple value, call the <see cref="BindingValue{T}"/> constructor or use an implicit
|
|||
/// conversion from <typeparamref name="T"/>
|
|||
/// - For an unset value, use <see cref="Unset"/> or simply `default`
|
|||
/// - For other types, call one of the static factory methods
|
|||
/// </remarks>
|
|||
public readonly struct BindingValue<T> |
|||
{ |
|||
private readonly T _value; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BindingValue{T}"/> struct with a type of
|
|||
/// <see cref="BindingValueType.Value"/>
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
public BindingValue(T value) |
|||
{ |
|||
ValidateValue(value); |
|||
_value = value; |
|||
Type = BindingValueType.Value; |
|||
Error = null; |
|||
} |
|||
|
|||
private BindingValue(BindingValueType type, T value, Exception? error) |
|||
{ |
|||
_value = value; |
|||
Type = type; |
|||
Error = error; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the binding value represents either a binding or data
|
|||
/// validation error.
|
|||
/// </summary>
|
|||
public bool HasError => Type.HasFlagCustom(BindingValueType.HasError); |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the binding value has a value.
|
|||
/// </summary>
|
|||
public bool HasValue => Type.HasFlagCustom(BindingValueType.HasValue); |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of the binding value.
|
|||
/// </summary>
|
|||
public BindingValueType Type { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the binding value or fallback value.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidOperationException">
|
|||
/// <see cref="HasValue"/> is false.
|
|||
/// </exception>
|
|||
public T Value => HasValue ? _value : throw new InvalidOperationException("BindingValue has no value."); |
|||
|
|||
/// <summary>
|
|||
/// Gets the binding or data validation error.
|
|||
/// </summary>
|
|||
public Exception? Error { get; } |
|||
|
|||
/// <summary>
|
|||
/// Converts the binding value to an <see cref="Optional{T}"/>.
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public Optional<T> ToOptional() => HasValue ? new Optional<T>(_value) : default; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => HasError ? $"Error: {Error!.Message}" : _value?.ToString() ?? "(null)"; |
|||
|
|||
/// <summary>
|
|||
/// Converts the value to untyped representation, using <see cref="AvaloniaProperty.UnsetValue"/>,
|
|||
/// <see cref="BindingOperations.DoNothing"/> and <see cref="BindingNotification"/> where
|
|||
/// appropriate.
|
|||
/// </summary>
|
|||
/// <returns>The untyped representation of the binding value.</returns>
|
|||
public object? ToUntyped() |
|||
{ |
|||
return Type switch |
|||
{ |
|||
BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue, |
|||
BindingValueType.DoNothing => BindingOperations.DoNothing, |
|||
BindingValueType.Value => _value, |
|||
BindingValueType.BindingError => |
|||
new BindingNotification(Error, BindingErrorType.Error), |
|||
BindingValueType.BindingErrorWithFallback => |
|||
new BindingNotification(Error, BindingErrorType.Error, Value), |
|||
BindingValueType.DataValidationError => |
|||
new BindingNotification(Error, BindingErrorType.DataValidationError), |
|||
BindingValueType.DataValidationErrorWithFallback => |
|||
new BindingNotification(Error, BindingErrorType.DataValidationError, Value), |
|||
_ => throw new NotSupportedException("Invalid BindingValueType."), |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new binding value with the specified value.
|
|||
/// </summary>
|
|||
/// <param name="value">The new value.</param>
|
|||
/// <returns>The new binding value.</returns>
|
|||
/// <exception cref="InvalidOperationException">
|
|||
/// The binding type is <see cref="BindingValueType.UnsetValue"/> or
|
|||
/// <see cref="BindingValueType.DoNothing"/>.
|
|||
/// </exception>
|
|||
public BindingValue<T> WithValue(T value) |
|||
{ |
|||
if (Type == BindingValueType.DoNothing) |
|||
{ |
|||
throw new InvalidOperationException("Cannot add value to DoNothing binding value."); |
|||
} |
|||
|
|||
var type = Type == BindingValueType.UnsetValue ? BindingValueType.Value : Type; |
|||
return new BindingValue<T>(type | BindingValueType.HasValue, value, Error); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the binding value if present, otherwise the default value.
|
|||
/// </summary>
|
|||
/// <returns>The value.</returns>
|
|||
public T GetValueOrDefault() => HasValue ? _value : default; |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the binding value if present, otherwise a default value.
|
|||
/// </summary>
|
|||
/// <param name="defaultValue">The default value.</param>
|
|||
/// <returns>The value.</returns>
|
|||
public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue; |
|||
|
|||
/// <summary>
|
|||
/// Gets the value if present, otherwise the default value.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The value if present and of the correct type, `default(TResult)` if the value is
|
|||
/// not present or of an incorrect type.
|
|||
/// </returns>
|
|||
public TResult GetValueOrDefault<TResult>() |
|||
{ |
|||
return HasValue ? |
|||
_value is TResult result ? result : default |
|||
: default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the binding value if present, otherwise a default value.
|
|||
/// </summary>
|
|||
/// <param name="defaultValue">The default value.</param>
|
|||
/// <returns>
|
|||
/// The value if present and of the correct type, `default(TResult)` if the value is
|
|||
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
|
|||
/// value is not present.
|
|||
/// </returns>
|
|||
public TResult GetValueOrDefault<TResult>(TResult defaultValue) |
|||
{ |
|||
return HasValue ? |
|||
_value is TResult result ? result : default |
|||
: defaultValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="BindingValue{T}"/> from an object, handling the special values
|
|||
/// <see cref="AvaloniaProperty.UnsetValue"/> and <see cref="BindingOperations.DoNothing"/>.
|
|||
/// </summary>
|
|||
/// <param name="value">The untyped value.</param>
|
|||
/// <returns>The typed binding value.</returns>
|
|||
public static BindingValue<T> FromUntyped(object? value) |
|||
{ |
|||
return value switch |
|||
{ |
|||
UnsetValueType _ => Unset, |
|||
DoNothingType _ => DoNothing, |
|||
BindingNotification n => n.ToBindingValue().Cast<T>(), |
|||
_ => (T)value |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a binding value from an instance of the underlying value type.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
public static implicit operator BindingValue<T>(T value) => new BindingValue<T>(value); |
|||
|
|||
/// <summary>
|
|||
/// Creates a binding value from an <see cref="Optional{T}"/>.
|
|||
/// </summary>
|
|||
/// <param name="optional">The optional value.</param>
|
|||
|
|||
public static implicit operator BindingValue<T>(Optional<T> optional) |
|||
{ |
|||
return optional.HasValue ? optional.Value : Unset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.UnsetValue"/>.
|
|||
/// </summary>
|
|||
public static BindingValue<T> Unset => new BindingValue<T>(BindingValueType.UnsetValue, default, null); |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.DoNothing"/>.
|
|||
/// </summary>
|
|||
public static BindingValue<T> DoNothing => new BindingValue<T>(BindingValueType.DoNothing, default, null); |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.BindingError"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The binding error.</param>
|
|||
public static BindingValue<T> BindingError(Exception e) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException("e"); |
|||
|
|||
return new BindingValue<T>(BindingValueType.BindingError, default, e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.BindingErrorWithFallback"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The binding error.</param>
|
|||
/// <param name="fallbackValue">The fallback value.</param>
|
|||
public static BindingValue<T> BindingError(Exception e, T fallbackValue) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException("e"); |
|||
|
|||
return new BindingValue<T>(BindingValueType.BindingErrorWithFallback, fallbackValue, e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.BindingError"/> or
|
|||
/// <see cref="BindingValueType.BindingErrorWithFallback"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The binding error.</param>
|
|||
/// <param name="fallbackValue">The fallback value.</param>
|
|||
public static BindingValue<T> BindingError(Exception e, Optional<T> fallbackValue) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException("e"); |
|||
|
|||
return new BindingValue<T>( |
|||
fallbackValue.HasValue ? |
|||
BindingValueType.BindingErrorWithFallback : |
|||
BindingValueType.BindingError, |
|||
fallbackValue.HasValue ? fallbackValue.Value : default, |
|||
e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.DataValidationError"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The data validation error.</param>
|
|||
public static BindingValue<T> DataValidationError(Exception e) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException("e"); |
|||
|
|||
return new BindingValue<T>(BindingValueType.DataValidationError, default, e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.DataValidationErrorWithFallback"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The data validation error.</param>
|
|||
/// <param name="fallbackValue">The fallback value.</param>
|
|||
public static BindingValue<T> DataValidationError(Exception e, T fallbackValue) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException("e"); |
|||
|
|||
return new BindingValue<T>(BindingValueType.DataValidationErrorWithFallback, fallbackValue, e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a binding value with a type of <see cref="BindingValueType.DataValidationError"/> or
|
|||
/// <see cref="BindingValueType.DataValidationErrorWithFallback"/>.
|
|||
/// </summary>
|
|||
/// <param name="e">The binding error.</param>
|
|||
/// <param name="fallbackValue">The fallback value.</param>
|
|||
public static BindingValue<T> DataValidationError(Exception e, Optional<T> fallbackValue) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException("e"); |
|||
|
|||
return new BindingValue<T>( |
|||
fallbackValue.HasValue ? |
|||
BindingValueType.DataValidationError : |
|||
BindingValueType.DataValidationErrorWithFallback, |
|||
fallbackValue.HasValue ? fallbackValue.Value : default, |
|||
e); |
|||
} |
|||
|
|||
private static void ValidateValue(T value) |
|||
{ |
|||
if (value is UnsetValueType) |
|||
{ |
|||
throw new InvalidOperationException("AvaloniaValue.UnsetValue is not a valid value for BindingValue<>."); |
|||
} |
|||
|
|||
if (value is DoNothingType) |
|||
{ |
|||
throw new InvalidOperationException("BindingOperations.DoNothing is not a valid value for BindingValue<>."); |
|||
} |
|||
|
|||
if (value is BindingValue<object>) |
|||
{ |
|||
throw new InvalidOperationException("BindingValue<object> cannot be wrapped in a BindingValue<>."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static class BindingValueExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Casts the type of a <see cref="BindingValue{T}"/> using only the C# cast operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The target type.</typeparam>
|
|||
/// <param name="value">The binding value.</param>
|
|||
/// <returns>The cast value.</returns>
|
|||
public static BindingValue<T> Cast<T>(this BindingValue<object> value) |
|||
{ |
|||
return value.Type switch |
|||
{ |
|||
BindingValueType.DoNothing => BindingValue<T>.DoNothing, |
|||
BindingValueType.UnsetValue => BindingValue<T>.Unset, |
|||
BindingValueType.Value => new BindingValue<T>((T)value.Value), |
|||
BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!), |
|||
BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError( |
|||
value.Error!, |
|||
(T)value.Value), |
|||
BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!), |
|||
BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError( |
|||
value.Error!, |
|||
(T)value.Value), |
|||
_ => throw new NotSupportedException("Invalid BindingValue type."), |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Casts the type of a <see cref="BindingValue{T}"/> using the implicit conversions
|
|||
/// allowed by the C# language.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The target type.</typeparam>
|
|||
/// <param name="value">The binding value.</param>
|
|||
/// <returns>The cast value.</returns>
|
|||
/// <remarks>
|
|||
/// Note that this method uses reflection and as such may be slow.
|
|||
/// </remarks>
|
|||
public static BindingValue<T> Convert<T>(this BindingValue<object> value) |
|||
{ |
|||
return value.Type switch |
|||
{ |
|||
BindingValueType.DoNothing => BindingValue<T>.DoNothing, |
|||
BindingValueType.UnsetValue => BindingValue<T>.Unset, |
|||
BindingValueType.Value => new BindingValue<T>(TypeUtilities.ConvertImplicit<T>(value.Value)), |
|||
BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!), |
|||
BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError( |
|||
value.Error!, |
|||
TypeUtilities.ConvertImplicit<T>(value.Value)), |
|||
BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!), |
|||
BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError( |
|||
value.Error!, |
|||
TypeUtilities.ConvertImplicit<T>(value.Value)), |
|||
_ => throw new NotSupportedException("Invalid BindingValue type."), |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// An optional typed value.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type.</typeparam>
|
|||
/// <remarks>
|
|||
/// This struct is similar to <see cref="Nullable{T}"/> except it also accepts reference types:
|
|||
/// note that null is a valid value for reference types. It is also similar to
|
|||
/// <see cref="BindingValue{T}"/> but has only two states: "value present" and "value missing".
|
|||
///
|
|||
/// To create a new optional value you can:
|
|||
///
|
|||
/// - For a simple value, call the <see cref="Optional{T}"/> constructor or use an implicit
|
|||
/// conversion from <typeparamref name="T"/>
|
|||
/// - For an missing value, use <see cref="Empty"/> or simply `default`
|
|||
/// </remarks>
|
|||
public readonly struct Optional<T> : IEquatable<Optional<T>> |
|||
{ |
|||
private readonly T _value; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Optional{T}"/> struct with value.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
public Optional(T value) |
|||
{ |
|||
_value = value; |
|||
HasValue = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether a value is present.
|
|||
/// </summary>
|
|||
public bool HasValue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidOperationException">
|
|||
/// <see cref="HasValue"/> is false.
|
|||
/// </exception>
|
|||
public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value."); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is Optional<T> o && this == o; |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(Optional<T> other) => this == other; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HasValue ? _value?.GetHashCode() ?? 0 : 0; |
|||
|
|||
/// <summary>
|
|||
/// Casts the value (if any) to an <see cref="object"/>.
|
|||
/// </summary>
|
|||
/// <returns>The cast optional value.</returns>
|
|||
public Optional<object> ToObject() => HasValue ? new Optional<object>(_value) : default; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)"; |
|||
|
|||
/// <summary>
|
|||
/// Gets the value if present, otherwise the default value.
|
|||
/// </summary>
|
|||
/// <returns>The value.</returns>
|
|||
public T GetValueOrDefault() => HasValue ? _value : default; |
|||
|
|||
/// <summary>
|
|||
/// Gets the value if present, otherwise a default value.
|
|||
/// </summary>
|
|||
/// <param name="defaultValue">The default value.</param>
|
|||
/// <returns>The value.</returns>
|
|||
public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue; |
|||
|
|||
/// <summary>
|
|||
/// Gets the value if present, otherwise the default value.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The value if present and of the correct type, `default(TResult)` if the value is
|
|||
/// not present or of an incorrect type.
|
|||
/// </returns>
|
|||
public TResult GetValueOrDefault<TResult>() |
|||
{ |
|||
return HasValue ? |
|||
_value is TResult result ? result : default |
|||
: default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value if present, otherwise a default value.
|
|||
/// </summary>
|
|||
/// <param name="defaultValue">The default value.</param>
|
|||
/// <returns>
|
|||
/// The value if present and of the correct type, `default(TResult)` if the value is
|
|||
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
|
|||
/// value is not present.
|
|||
/// </returns>
|
|||
public TResult GetValueOrDefault<TResult>(TResult defaultValue) |
|||
{ |
|||
return HasValue ? |
|||
_value is TResult result ? result : default |
|||
: defaultValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Optional{T}"/> from an instance of the underlying value type.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
public static implicit operator Optional<T>(T value) => new Optional<T>(value); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Optional{T}"/>s for inequality.
|
|||
/// </summary>
|
|||
/// <param name="x">The first value.</param>
|
|||
/// <param name="y">The second value.</param>
|
|||
/// <returns>True if the values are unequal; otherwise false.</returns>
|
|||
public static bool operator !=(Optional<T> x, Optional<T> y) => !(x == y); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Optional{T}"/>s for equality.
|
|||
/// </summary>
|
|||
/// <param name="x">The first value.</param>
|
|||
/// <param name="y">The second value.</param>
|
|||
/// <returns>True if the values are equal; otherwise false.</returns>
|
|||
public static bool operator==(Optional<T> x, Optional<T> y) |
|||
{ |
|||
if (!x.HasValue && !y.HasValue) |
|||
{ |
|||
return true; |
|||
} |
|||
else if (x.HasValue && y.HasValue) |
|||
{ |
|||
return EqualityComparer<T>.Default.Equals(x.Value, y.Value); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns an <see cref="Optional{T}"/> without a value.
|
|||
/// </summary>
|
|||
public static Optional<T> Empty => default; |
|||
} |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
using Avalonia.Reactive; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for direct properties.
|
|||
/// </summary>
|
|||
/// <typeparam name="TValue">The type of the property's value.</typeparam>
|
|||
/// <remarks>
|
|||
/// Whereas <see cref="DirectProperty{TOwner, TValue}"/> is typed on the owner type, this base
|
|||
/// class provides a non-owner-typed interface to a direct poperty.
|
|||
/// </remarks>
|
|||
public abstract class DirectPropertyBase<TValue> : AvaloniaProperty<TValue> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="name">The name of the property.</param>
|
|||
/// <param name="ownerType">The type of the class that registers the property.</param>
|
|||
/// <param name="metadata">The property metadata.</param>
|
|||
/// <param name="enableDataValidation">
|
|||
/// Whether the property is interested in data validation.
|
|||
/// </param>
|
|||
protected DirectPropertyBase( |
|||
string name, |
|||
Type ownerType, |
|||
PropertyMetadata metadata, |
|||
bool enableDataValidation) |
|||
: base(name, ownerType, metadata) |
|||
{ |
|||
IsDataValidationEnabled = enableDataValidation; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
|
|||
/// </summary>
|
|||
/// <param name="source">The property to copy.</param>
|
|||
/// <param name="ownerType">The new owner type.</param>
|
|||
/// <param name="metadata">Optional overridden metadata.</param>
|
|||
/// <param name="enableDataValidation">
|
|||
/// Whether the property is interested in data validation.
|
|||
/// </param>
|
|||
protected DirectPropertyBase( |
|||
AvaloniaProperty source, |
|||
Type ownerType, |
|||
PropertyMetadata metadata, |
|||
bool enableDataValidation) |
|||
: base(source, ownerType, metadata) |
|||
{ |
|||
IsDataValidationEnabled = enableDataValidation; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the type that registered the property.
|
|||
/// </summary>
|
|||
public abstract Type Owner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates whether data validation is enabled for the property.
|
|||
/// </summary>
|
|||
public bool IsDataValidationEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the property on the instance.
|
|||
/// </summary>
|
|||
/// <param name="instance">The instance.</param>
|
|||
/// <returns>The property value.</returns>
|
|||
internal abstract TValue InvokeGetter(IAvaloniaObject instance); |
|||
|
|||
/// <summary>
|
|||
/// Sets the value of the property on the instance.
|
|||
/// </summary>
|
|||
/// <param name="instance">The instance.</param>
|
|||
/// <param name="value">The value.</param>
|
|||
internal abstract void InvokeSetter(IAvaloniaObject instance, BindingValue<TValue> value); |
|||
|
|||
/// <summary>
|
|||
/// Gets the unset value for the property on the specified type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
/// <returns>The unset value.</returns>
|
|||
public TValue GetUnsetValue(Type type) |
|||
{ |
|||
type = type ?? throw new ArgumentNullException(nameof(type)); |
|||
return GetMetadata(type).UnsetValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the property metadata for the specified type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
/// <returns>
|
|||
/// The property metadata.
|
|||
/// </returns>
|
|||
public new DirectPropertyMetadata<TValue> GetMetadata(Type type) |
|||
{ |
|||
return (DirectPropertyMetadata<TValue>)base.GetMetadata(type); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override void NotifyInitialized(IAvaloniaObject o) |
|||
{ |
|||
if (HasNotifyInitializedObservers) |
|||
{ |
|||
var e = new AvaloniaPropertyChangedEventArgs<TValue>( |
|||
o, |
|||
this, |
|||
default, |
|||
InvokeGetter(o), |
|||
BindingPriority.Unset); |
|||
NotifyInitialized(e); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override void RouteClearValue(IAvaloniaObject o) |
|||
{ |
|||
o.ClearValue<TValue>(this); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override object? RouteGetValue(IAvaloniaObject o) |
|||
{ |
|||
return o.GetValue<TValue>(this); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override void RouteSetValue( |
|||
IAvaloniaObject o, |
|||
object value, |
|||
BindingPriority priority) |
|||
{ |
|||
var v = TryConvert(value); |
|||
|
|||
if (v.HasValue) |
|||
{ |
|||
o.SetValue<TValue>(this, (TValue)v.Value); |
|||
} |
|||
else if (v.Type == BindingValueType.UnsetValue) |
|||
{ |
|||
o.ClearValue(this); |
|||
} |
|||
else if (v.HasError) |
|||
{ |
|||
throw v.Error!; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override IDisposable RouteBind( |
|||
IAvaloniaObject o, |
|||
IObservable<BindingValue<object>> source, |
|||
BindingPriority priority) |
|||
{ |
|||
var adapter = TypedBindingAdapter<TValue>.Create(o, this, source); |
|||
return o.Bind<TValue>(this, adapter); |
|||
} |
|||
|
|||
internal override void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject oldParent) |
|||
{ |
|||
throw new NotSupportedException("Direct properties do not support inheritance."); |
|||
} |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Data; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// An owner of a <see cref="PriorityValue"/>.
|
|||
/// </summary>
|
|||
internal interface IPriorityValueOwner |
|||
{ |
|||
/// <summary>
|
|||
/// Called when a <see cref="PriorityValue"/>'s value changes.
|
|||
/// </summary>
|
|||
/// <param name="property">The the property that has changed.</param>
|
|||
/// <param name="priority">The priority of the value.</param>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue); |
|||
|
|||
/// <summary>
|
|||
/// Called when a <see cref="BindingNotification"/> is received by a
|
|||
/// <see cref="PriorityValue"/>.
|
|||
/// </summary>
|
|||
/// <param name="property">The the property that has changed.</param>
|
|||
/// <param name="notification">The notification.</param>
|
|||
void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification); |
|||
|
|||
/// <summary>
|
|||
/// Returns deferred setter for given non-direct property.
|
|||
/// </summary>
|
|||
/// <param name="property">Property.</param>
|
|||
/// <returns>Deferred setter for given property.</returns>
|
|||
DeferredSetter<object> GetNonDirectDeferredSetter(AvaloniaProperty property); |
|||
|
|||
/// <summary>
|
|||
/// Logs a binding error.
|
|||
/// </summary>
|
|||
/// <param name="property">The property the error occurred on.</param>
|
|||
/// <param name="e">The binding error.</param>
|
|||
void LogError(AvaloniaProperty property, Exception e); |
|||
|
|||
/// <summary>
|
|||
/// Ensures that the current thread is the UI thread.
|
|||
/// </summary>
|
|||
void VerifyAccess(); |
|||
} |
|||
} |
|||
@ -1,160 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Runtime.ExceptionServices; |
|||
using Avalonia.Data; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// A registered binding in a <see cref="PriorityValue"/>.
|
|||
/// </summary>
|
|||
internal class PriorityBindingEntry : IDisposable, IObserver<object> |
|||
{ |
|||
private readonly PriorityLevel _owner; |
|||
private IDisposable _subscription; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PriorityBindingEntry"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner">The owner.</param>
|
|||
/// <param name="index">
|
|||
/// The binding index. Later bindings should have higher indexes.
|
|||
/// </param>
|
|||
public PriorityBindingEntry(PriorityLevel owner, int index) |
|||
{ |
|||
_owner = owner; |
|||
Index = index; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the observable associated with the entry.
|
|||
/// </summary>
|
|||
public IObservable<object> Observable { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a description of the binding.
|
|||
/// </summary>
|
|||
public string Description |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the binding entry index. Later bindings will have higher indexes.
|
|||
/// </summary>
|
|||
public int Index |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the binding has completed.
|
|||
/// </summary>
|
|||
public bool HasCompleted { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// The current value of the binding.
|
|||
/// </summary>
|
|||
public object Value |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts listening to the binding.
|
|||
/// </summary>
|
|||
/// <param name="binding">The binding.</param>
|
|||
public void Start(IObservable<object> binding) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(binding != null); |
|||
|
|||
if (_subscription != null) |
|||
{ |
|||
throw new Exception("PriorityValue.Entry.Start() called more than once."); |
|||
} |
|||
|
|||
Observable = binding; |
|||
Value = AvaloniaProperty.UnsetValue; |
|||
|
|||
if (binding is IDescription) |
|||
{ |
|||
Description = ((IDescription)binding).Description; |
|||
} |
|||
|
|||
_subscription = binding.Subscribe(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ends the binding subscription.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
} |
|||
|
|||
void IObserver<object>.OnNext(object value) |
|||
{ |
|||
void Signal(PriorityBindingEntry instance, object newValue) |
|||
{ |
|||
var notification = newValue as BindingNotification; |
|||
|
|||
if (notification != null) |
|||
{ |
|||
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) |
|||
{ |
|||
instance.Value = notification.Value; |
|||
instance._owner.Changed(instance); |
|||
} |
|||
|
|||
if (notification.ErrorType != BindingErrorType.None) |
|||
{ |
|||
instance._owner.Error(instance, notification); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
instance.Value = newValue; |
|||
instance._owner.Changed(instance); |
|||
} |
|||
} |
|||
|
|||
if (Dispatcher.UIThread.CheckAccess()) |
|||
{ |
|||
Signal(this, value); |
|||
} |
|||
else |
|||
{ |
|||
// To avoid allocating closure in the outer scope we need to capture variables
|
|||
// locally. This allows us to skip most of the allocations when on UI thread.
|
|||
var instance = this; |
|||
var newValue = value; |
|||
|
|||
Dispatcher.UIThread.Post(() => Signal(instance, newValue)); |
|||
} |
|||
} |
|||
|
|||
void IObserver<object>.OnCompleted() |
|||
{ |
|||
HasCompleted = true; |
|||
|
|||
if (Dispatcher.UIThread.CheckAccess()) |
|||
{ |
|||
_owner.Completed(this); |
|||
} |
|||
else |
|||
{ |
|||
Dispatcher.UIThread.Post(() => _owner.Completed(this)); |
|||
} |
|||
} |
|||
|
|||
void IObserver<object>.OnError(Exception error) |
|||
{ |
|||
ExceptionDispatchInfo.Capture(error).Throw(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,227 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Threading; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Stores bindings for a priority level in a <see cref="PriorityValue"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// Each priority level in a <see cref="PriorityValue"/> has a current <see cref="Value"/>,
|
|||
/// a list of <see cref="Bindings"/> and a <see cref="DirectValue"/>. When there are no
|
|||
/// bindings present, or all bindings return <see cref="AvaloniaProperty.UnsetValue"/> then
|
|||
/// <code>Value</code> will equal <code>DirectValue</code>.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// When there are bindings present, then the latest added binding that doesn't return
|
|||
/// <code>UnsetValue</code> will take precedence. The active binding is returned by the
|
|||
/// <see cref="ActiveBindingIndex"/> property (which refers to the active binding's
|
|||
/// <see cref="PriorityBindingEntry.Index"/> property rather than the index in
|
|||
/// <code>Bindings</code>).
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// If <code>DirectValue</code> is set while a binding is active, then it will replace the
|
|||
/// current value until the active binding fires again.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
internal class PriorityLevel |
|||
{ |
|||
private object _directValue; |
|||
private int _nextIndex; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PriorityLevel"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner">The owner.</param>
|
|||
/// <param name="priority">The priority.</param>
|
|||
public PriorityLevel( |
|||
PriorityValue owner, |
|||
int priority) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(owner != null); |
|||
|
|||
Owner = owner; |
|||
Priority = priority; |
|||
Value = _directValue = AvaloniaProperty.UnsetValue; |
|||
ActiveBindingIndex = -1; |
|||
Bindings = new LinkedList<PriorityBindingEntry>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the owner of the level.
|
|||
/// </summary>
|
|||
public PriorityValue Owner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the priority of this level.
|
|||
/// </summary>
|
|||
public int Priority { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the direct value for this priority level.
|
|||
/// </summary>
|
|||
public object DirectValue |
|||
{ |
|||
get |
|||
{ |
|||
return _directValue; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
Value = _directValue = value; |
|||
Owner.LevelValueChanged(this); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current binding for the priority level.
|
|||
/// </summary>
|
|||
public object Value { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="PriorityBindingEntry.Index"/> value of the active binding, or -1
|
|||
/// if no binding is active.
|
|||
/// </summary>
|
|||
public int ActiveBindingIndex { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bindings for the priority level.
|
|||
/// </summary>
|
|||
public LinkedList<PriorityBindingEntry> Bindings { get; } |
|||
|
|||
/// <summary>
|
|||
/// Adds a binding.
|
|||
/// </summary>
|
|||
/// <param name="binding">The binding to add.</param>
|
|||
/// <returns>A disposable used to remove the binding.</returns>
|
|||
public IDisposable Add(IObservable<object> binding) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(binding != null); |
|||
|
|||
var entry = new PriorityBindingEntry(this, _nextIndex++); |
|||
var node = Bindings.AddFirst(entry); |
|||
|
|||
entry.Start(binding); |
|||
|
|||
return new RemoveBindingDisposable(node, Bindings, this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invoked when an entry in <see cref="Bindings"/> changes value.
|
|||
/// </summary>
|
|||
/// <param name="entry">The entry that changed.</param>
|
|||
public void Changed(PriorityBindingEntry entry) |
|||
{ |
|||
if (entry.Index >= ActiveBindingIndex) |
|||
{ |
|||
if (entry.Value != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
Value = entry.Value; |
|||
ActiveBindingIndex = entry.Index; |
|||
Owner.LevelValueChanged(this); |
|||
} |
|||
else |
|||
{ |
|||
ActivateFirstBinding(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invoked when an entry in <see cref="Bindings"/> completes.
|
|||
/// </summary>
|
|||
/// <param name="entry">The entry that completed.</param>
|
|||
public void Completed(PriorityBindingEntry entry) |
|||
{ |
|||
Bindings.Remove(entry); |
|||
|
|||
if (entry.Index >= ActiveBindingIndex) |
|||
{ |
|||
ActivateFirstBinding(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invoked when an entry in <see cref="Bindings"/> encounters a recoverable error.
|
|||
/// </summary>
|
|||
/// <param name="entry">The entry that completed.</param>
|
|||
/// <param name="error">The error.</param>
|
|||
public void Error(PriorityBindingEntry entry, BindingNotification error) |
|||
{ |
|||
Owner.LevelError(this, error); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Activates the first binding that has a value.
|
|||
/// </summary>
|
|||
private void ActivateFirstBinding() |
|||
{ |
|||
foreach (var binding in Bindings) |
|||
{ |
|||
if (binding.Value != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
Value = binding.Value; |
|||
ActiveBindingIndex = binding.Index; |
|||
Owner.LevelValueChanged(this); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
Value = DirectValue; |
|||
ActiveBindingIndex = -1; |
|||
Owner.LevelValueChanged(this); |
|||
} |
|||
|
|||
private sealed class RemoveBindingDisposable : IDisposable |
|||
{ |
|||
private readonly LinkedList<PriorityBindingEntry> _bindings; |
|||
private readonly PriorityLevel _priorityLevel; |
|||
private LinkedListNode<PriorityBindingEntry> _binding; |
|||
|
|||
public RemoveBindingDisposable( |
|||
LinkedListNode<PriorityBindingEntry> binding, |
|||
LinkedList<PriorityBindingEntry> bindings, |
|||
PriorityLevel priorityLevel) |
|||
{ |
|||
_binding = binding; |
|||
_bindings = bindings; |
|||
_priorityLevel = priorityLevel; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
LinkedListNode<PriorityBindingEntry> binding = Interlocked.Exchange(ref _binding, null); |
|||
|
|||
if (binding == null) |
|||
{ |
|||
// Some system is trying to remove binding twice.
|
|||
Debug.Assert(false); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PriorityBindingEntry entry = binding.Value; |
|||
|
|||
if (!entry.HasCompleted) |
|||
{ |
|||
_bindings.Remove(binding); |
|||
|
|||
entry.Dispose(); |
|||
|
|||
if (entry.Index >= _priorityLevel.ActiveBindingIndex) |
|||
{ |
|||
_priorityLevel.ActivateFirstBinding(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,315 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using Avalonia.Data; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Maintains a list of prioritized bindings together with a current value.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Bindings, in the form of <see cref="IObservable{Object}"/>s are added to the object using
|
|||
/// the <see cref="Add"/> method. With the observable is passed a priority, where lower values
|
|||
/// represent higher priorities. The current <see cref="Value"/> is selected from the highest
|
|||
/// priority binding that doesn't return <see cref="AvaloniaProperty.UnsetValue"/>. Where there
|
|||
/// are multiple bindings registered with the same priority, the most recently added binding
|
|||
/// has a higher priority. Each time the value changes, the
|
|||
/// <see cref="IPriorityValueOwner.Changed"/> method on the
|
|||
/// owner object is fired with the old and new values.
|
|||
/// </remarks>
|
|||
internal sealed class PriorityValue : ISetAndNotifyHandler<(object,int)> |
|||
{ |
|||
private readonly Type _valueType; |
|||
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>(); |
|||
private readonly Func<object, object> _validate; |
|||
private (object value, int priority) _value; |
|||
private DeferredSetter<object> _setter; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner">The owner of the object.</param>
|
|||
/// <param name="property">The property that the value represents.</param>
|
|||
/// <param name="valueType">The value type.</param>
|
|||
/// <param name="validate">An optional validation function.</param>
|
|||
public PriorityValue( |
|||
IPriorityValueOwner owner, |
|||
AvaloniaProperty property, |
|||
Type valueType, |
|||
Func<object, object> validate = null) |
|||
{ |
|||
Owner = owner; |
|||
Property = property; |
|||
_valueType = valueType; |
|||
_value = (AvaloniaProperty.UnsetValue, int.MaxValue); |
|||
_validate = validate; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the property is animating.
|
|||
/// </summary>
|
|||
public bool IsAnimating |
|||
{ |
|||
get |
|||
{ |
|||
return ValuePriority <= (int)BindingPriority.Animation && |
|||
GetLevel(ValuePriority).ActiveBindingIndex != -1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the owner of the value.
|
|||
/// </summary>
|
|||
public IPriorityValueOwner Owner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the property that the value represents.
|
|||
/// </summary>
|
|||
public AvaloniaProperty Property { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the current value.
|
|||
/// </summary>
|
|||
public object Value => _value.value; |
|||
|
|||
/// <summary>
|
|||
/// Gets the priority of the binding that is currently active.
|
|||
/// </summary>
|
|||
public int ValuePriority => _value.priority; |
|||
|
|||
/// <summary>
|
|||
/// Adds a new binding.
|
|||
/// </summary>
|
|||
/// <param name="binding">The binding.</param>
|
|||
/// <param name="priority">The binding priority.</param>
|
|||
/// <returns>
|
|||
/// A disposable that will remove the binding.
|
|||
/// </returns>
|
|||
public IDisposable Add(IObservable<object> binding, int priority) |
|||
{ |
|||
return GetLevel(priority).Add(binding); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the value for a specified priority.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
/// <param name="priority">The priority</param>
|
|||
public void SetValue(object value, int priority) |
|||
{ |
|||
GetLevel(priority).DirectValue = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the currently active bindings on this object.
|
|||
/// </summary>
|
|||
/// <returns>An enumerable collection of bindings.</returns>
|
|||
public IEnumerable<PriorityBindingEntry> GetBindings() |
|||
{ |
|||
foreach (var level in _levels) |
|||
{ |
|||
foreach (var binding in level.Value.Bindings) |
|||
{ |
|||
yield return binding; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns diagnostic string that can help the user debug the bindings in effect on
|
|||
/// this object.
|
|||
/// </summary>
|
|||
/// <returns>A diagnostic string.</returns>
|
|||
public string GetDiagnostic() |
|||
{ |
|||
var b = new StringBuilder(); |
|||
var first = true; |
|||
|
|||
foreach (var level in _levels) |
|||
{ |
|||
if (!first) |
|||
{ |
|||
b.AppendLine(); |
|||
} |
|||
|
|||
b.Append(ValuePriority == level.Key ? "*" : string.Empty); |
|||
b.Append("Priority "); |
|||
b.Append(level.Key); |
|||
b.Append(": "); |
|||
b.AppendLine(level.Value.Value?.ToString() ?? "(null)"); |
|||
b.AppendLine("--------"); |
|||
b.Append("Direct: "); |
|||
b.AppendLine(level.Value.DirectValue?.ToString() ?? "(null)"); |
|||
|
|||
foreach (var binding in level.Value.Bindings) |
|||
{ |
|||
b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : string.Empty); |
|||
b.Append(binding.Description ?? binding.Observable.GetType().Name); |
|||
b.Append(": "); |
|||
b.AppendLine(binding.Value?.ToString() ?? "(null)"); |
|||
} |
|||
|
|||
first = false; |
|||
} |
|||
|
|||
return b.ToString(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the value for a priority level changes.
|
|||
/// </summary>
|
|||
/// <param name="level">The priority level of the changed entry.</param>
|
|||
public void LevelValueChanged(PriorityLevel level) |
|||
{ |
|||
if (level.Priority <= ValuePriority) |
|||
{ |
|||
if (level.Value != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
UpdateValue(level.Value, level.Priority); |
|||
} |
|||
else |
|||
{ |
|||
foreach (var i in _levels.Values.OrderBy(x => x.Priority)) |
|||
{ |
|||
if (i.Value != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
UpdateValue(i.Value, i.Priority); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
UpdateValue(AvaloniaProperty.UnsetValue, int.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when a priority level encounters an error.
|
|||
/// </summary>
|
|||
/// <param name="level">The priority level of the changed entry.</param>
|
|||
/// <param name="error">The binding error.</param>
|
|||
public void LevelError(PriorityLevel level, BindingNotification error) |
|||
{ |
|||
Owner.LogError(Property, error.Error); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Causes a revalidation of the value.
|
|||
/// </summary>
|
|||
public void Revalidate() |
|||
{ |
|||
if (_validate != null) |
|||
{ |
|||
PriorityLevel level; |
|||
|
|||
if (_levels.TryGetValue(ValuePriority, out level)) |
|||
{ |
|||
UpdateValue(level.Value, level.Priority); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="PriorityLevel"/> with the specified priority, creating it if it
|
|||
/// doesn't already exist.
|
|||
/// </summary>
|
|||
/// <param name="priority">The priority.</param>
|
|||
/// <returns>The priority level.</returns>
|
|||
private PriorityLevel GetLevel(int priority) |
|||
{ |
|||
PriorityLevel result; |
|||
|
|||
if (!_levels.TryGetValue(priority, out result)) |
|||
{ |
|||
result = new PriorityLevel(this, priority); |
|||
_levels.Add(priority, result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the current <see cref="Value"/> and notifies all subscribers.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to set.</param>
|
|||
/// <param name="priority">The priority level that the value came from.</param>
|
|||
private void UpdateValue(object value, int priority) |
|||
{ |
|||
var newValue = (value, priority); |
|||
|
|||
if (newValue == _value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (_setter == null) |
|||
{ |
|||
_setter = Owner.GetNonDirectDeferredSetter(Property); |
|||
} |
|||
|
|||
_setter.SetAndNotifyCallback(Property, this, ref _value, newValue); |
|||
} |
|||
|
|||
void ISetAndNotifyHandler<(object, int)>.HandleSetAndNotify(AvaloniaProperty property, ref (object, int) backing, (object, int) value) |
|||
{ |
|||
SetAndNotify(ref backing, value); |
|||
} |
|||
|
|||
private void SetAndNotify(ref (object value, int priority) backing, (object value, int priority) update) |
|||
{ |
|||
var val = update.value; |
|||
var notification = val as BindingNotification; |
|||
object castValue; |
|||
|
|||
if (notification != null) |
|||
{ |
|||
val = (notification.HasValue) ? notification.Value : null; |
|||
} |
|||
|
|||
if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue)) |
|||
{ |
|||
var old = backing.value; |
|||
|
|||
if (_validate != null && castValue != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
castValue = _validate(castValue); |
|||
} |
|||
|
|||
backing = (castValue, update.priority); |
|||
|
|||
if (notification?.HasValue == true) |
|||
{ |
|||
notification.SetValue(castValue); |
|||
} |
|||
|
|||
if (notification == null || notification.HasValue) |
|||
{ |
|||
Owner?.Changed(Property, ValuePriority, old, Value); |
|||
} |
|||
|
|||
if (notification != null) |
|||
{ |
|||
Owner?.BindingNotificationReceived(Property, notification); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Logger.TryGet(LogEventLevel.Error)?.Log( |
|||
LogArea.Binding, |
|||
Owner, |
|||
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", |
|||
Property.Name, |
|||
_valueType, |
|||
val, |
|||
val?.GetType()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
using Avalonia.Threading; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
|
|||
/// </summary>
|
|||
internal interface IBindingEntry : IPriorityValueEntry, IDisposable |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores a binding in a <see cref="ValueStore"/> or <see cref="PriorityValue{T}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>> |
|||
{ |
|||
private readonly IAvaloniaObject _owner; |
|||
private IValueSink _sink; |
|||
private IDisposable? _subscription; |
|||
|
|||
public BindingEntry( |
|||
IAvaloniaObject owner, |
|||
StyledPropertyBase<T> property, |
|||
IObservable<BindingValue<T>> source, |
|||
BindingPriority priority, |
|||
IValueSink sink) |
|||
{ |
|||
_owner = owner; |
|||
Property = property; |
|||
Source = source; |
|||
Priority = priority; |
|||
_sink = sink; |
|||
} |
|||
|
|||
public StyledPropertyBase<T> Property { get; } |
|||
public BindingPriority Priority { get; } |
|||
public IObservable<BindingValue<T>> Source { get; } |
|||
public Optional<T> Value { get; private set; } |
|||
Optional<object> IValue.Value => Value.ToObject(); |
|||
BindingPriority IValue.ValuePriority => Priority; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
_subscription = null; |
|||
_sink.Completed(Property, this); |
|||
} |
|||
|
|||
public void OnCompleted() => _sink.Completed(Property, this); |
|||
|
|||
public void OnError(Exception error) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
public void OnNext(BindingValue<T> value) |
|||
{ |
|||
if (Dispatcher.UIThread.CheckAccess()) |
|||
{ |
|||
UpdateValue(value); |
|||
} |
|||
else |
|||
{ |
|||
// To avoid allocating closure in the outer scope we need to capture variables
|
|||
// locally. This allows us to skip most of the allocations when on UI thread.
|
|||
var instance = this; |
|||
var newValue = value; |
|||
|
|||
Dispatcher.UIThread.Post(() => instance.UpdateValue(newValue)); |
|||
} |
|||
} |
|||
|
|||
public void Start() |
|||
{ |
|||
_subscription = Source.Subscribe(this); |
|||
} |
|||
|
|||
public void Reparent(IValueSink sink) => _sink = sink; |
|||
|
|||
private void UpdateValue(BindingValue<T> value) |
|||
{ |
|||
if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false) |
|||
{ |
|||
value = Property.GetDefaultValue(_owner.GetType()); |
|||
} |
|||
|
|||
if (value.Type == BindingValueType.DoNothing) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var old = Value; |
|||
|
|||
if (value.Type != BindingValueType.DataValidationError) |
|||
{ |
|||
Value = value.ToOptional(); |
|||
} |
|||
|
|||
_sink.ValueChanged(Property, Priority, old, value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Stores a value with a priority in a <see cref="ValueStore"/> or
|
|||
/// <see cref="PriorityValue{T}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
internal class ConstantValueEntry<T> : IPriorityValueEntry<T> |
|||
{ |
|||
public ConstantValueEntry( |
|||
StyledPropertyBase<T> property, |
|||
T value, |
|||
BindingPriority priority) |
|||
{ |
|||
Property = property; |
|||
Value = value; |
|||
Priority = priority; |
|||
} |
|||
|
|||
public StyledPropertyBase<T> Property { get; } |
|||
public BindingPriority Priority { get; } |
|||
public Optional<T> Value { get; } |
|||
Optional<object> IValue.Value => Value.ToObject(); |
|||
BindingPriority IValue.ValuePriority => Priority; |
|||
|
|||
public void Reparent(IValueSink sink) { } |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an untyped interface to <see cref="IPriorityValueEntry{T}"/>.
|
|||
/// </summary>
|
|||
internal interface IPriorityValueEntry : IValue |
|||
{ |
|||
BindingPriority Priority { get; } |
|||
|
|||
void Reparent(IValueSink sink); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an object that can act as an entry in a <see cref="PriorityValue{T}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an untyped interface to <see cref="IValue{T}"/>.
|
|||
/// </summary>
|
|||
internal interface IValue |
|||
{ |
|||
Optional<object> Value { get; } |
|||
BindingPriority ValuePriority { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an object that can act as an entry in a <see cref="ValueStore"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
internal interface IValue<T> : IValue |
|||
{ |
|||
new Optional<T> Value { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
|
|||
/// </summary>
|
|||
internal interface IValueSink |
|||
{ |
|||
void ValueChanged<T>( |
|||
StyledPropertyBase<T> property, |
|||
BindingPriority priority, |
|||
Optional<T> oldValue, |
|||
BindingValue<T> newValue); |
|||
|
|||
void Completed(AvaloniaProperty property, IPriorityValueEntry entry); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Stores a value with local value priority in a <see cref="ValueStore"/> or
|
|||
/// <see cref="PriorityValue{T}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
internal class LocalValueEntry<T> : IValue<T> |
|||
{ |
|||
private T _value; |
|||
|
|||
public LocalValueEntry(T value) => _value = value; |
|||
public Optional<T> Value => _value; |
|||
public BindingPriority ValuePriority => BindingPriority.LocalValue; |
|||
Optional<object> IValue.Value => Value.ToObject(); |
|||
public void SetValue(T value) => _value = value; |
|||
} |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.PropertyStore |
|||
{ |
|||
/// <summary>
|
|||
/// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
/// <remarks>
|
|||
/// When more than a single value or binding is applied to a property in an
|
|||
/// <see cref="AvaloniaObject"/>, the entry in the <see cref="ValueStore"/> is converted into
|
|||
/// a <see cref="PriorityValue{T}"/>. This class holds any number of
|
|||
/// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
|
|||
/// they were added) plus a local value.
|
|||
/// </remarks>
|
|||
internal class PriorityValue<T> : IValue<T>, IValueSink |
|||
{ |
|||
private readonly IAvaloniaObject _owner; |
|||
private readonly IValueSink _sink; |
|||
private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>(); |
|||
private readonly Func<IAvaloniaObject, T, T>? _coerceValue; |
|||
private Optional<T> _localValue; |
|||
|
|||
public PriorityValue( |
|||
IAvaloniaObject owner, |
|||
StyledPropertyBase<T> property, |
|||
IValueSink sink) |
|||
{ |
|||
_owner = owner; |
|||
Property = property; |
|||
_sink = sink; |
|||
|
|||
if (property.HasCoercion) |
|||
{ |
|||
var metadata = property.GetMetadata(owner.GetType()); |
|||
_coerceValue = metadata.CoerceValue; |
|||
} |
|||
} |
|||
|
|||
public PriorityValue( |
|||
IAvaloniaObject owner, |
|||
StyledPropertyBase<T> property, |
|||
IValueSink sink, |
|||
IPriorityValueEntry<T> existing) |
|||
: this(owner, property, sink) |
|||
{ |
|||
existing.Reparent(this); |
|||
_entries.Add(existing); |
|||
|
|||
if (existing.Value.HasValue) |
|||
{ |
|||
Value = existing.Value; |
|||
ValuePriority = existing.Priority; |
|||
} |
|||
} |
|||
|
|||
public PriorityValue( |
|||
IAvaloniaObject owner, |
|||
StyledPropertyBase<T> property, |
|||
IValueSink sink, |
|||
LocalValueEntry<T> existing) |
|||
: this(owner, property, sink) |
|||
{ |
|||
_localValue = existing.Value; |
|||
Value = _localValue; |
|||
ValuePriority = BindingPriority.LocalValue; |
|||
} |
|||
|
|||
public StyledPropertyBase<T> Property { get; } |
|||
public Optional<T> Value { get; private set; } |
|||
public BindingPriority ValuePriority { get; private set; } |
|||
public IReadOnlyList<IPriorityValueEntry<T>> Entries => _entries; |
|||
Optional<object> IValue.Value => Value.ToObject(); |
|||
|
|||
public void ClearLocalValue() => UpdateEffectiveValue(); |
|||
|
|||
public void SetValue(T value, BindingPriority priority) |
|||
{ |
|||
if (priority == BindingPriority.LocalValue) |
|||
{ |
|||
_localValue = value; |
|||
} |
|||
else |
|||
{ |
|||
var insert = FindInsertPoint(priority); |
|||
_entries.Insert(insert, new ConstantValueEntry<T>(Property, value, priority)); |
|||
} |
|||
|
|||
UpdateEffectiveValue(); |
|||
} |
|||
|
|||
public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority) |
|||
{ |
|||
var binding = new BindingEntry<T>(_owner, Property, source, priority, this); |
|||
var insert = FindInsertPoint(binding.Priority); |
|||
_entries.Insert(insert, binding); |
|||
return binding; |
|||
} |
|||
|
|||
public void CoerceValue() => UpdateEffectiveValue(); |
|||
|
|||
void IValueSink.ValueChanged<TValue>( |
|||
StyledPropertyBase<TValue> property, |
|||
BindingPriority priority, |
|||
Optional<TValue> oldValue, |
|||
BindingValue<TValue> newValue) |
|||
{ |
|||
if (priority == BindingPriority.LocalValue) |
|||
{ |
|||
_localValue = default; |
|||
} |
|||
|
|||
UpdateEffectiveValue(); |
|||
} |
|||
|
|||
void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) |
|||
{ |
|||
_entries.Remove((IPriorityValueEntry<T>)entry); |
|||
UpdateEffectiveValue(); |
|||
} |
|||
|
|||
private int FindInsertPoint(BindingPriority priority) |
|||
{ |
|||
var result = _entries.Count; |
|||
|
|||
for (var i = 0; i < _entries.Count; ++i) |
|||
{ |
|||
if (_entries[i].Priority < priority) |
|||
{ |
|||
result = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private void UpdateEffectiveValue() |
|||
{ |
|||
var reachedLocalValues = false; |
|||
var value = default(Optional<T>); |
|||
|
|||
if (_entries.Count > 0) |
|||
{ |
|||
for (var i = _entries.Count - 1; i >= 0; --i) |
|||
{ |
|||
var entry = _entries[i]; |
|||
|
|||
if (!reachedLocalValues && entry.Priority >= BindingPriority.LocalValue) |
|||
{ |
|||
reachedLocalValues = true; |
|||
|
|||
if (_localValue.HasValue) |
|||
{ |
|||
value = _localValue; |
|||
ValuePriority = BindingPriority.LocalValue; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (entry.Value.HasValue) |
|||
{ |
|||
value = entry.Value; |
|||
ValuePriority = entry.Priority; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else if (_localValue.HasValue) |
|||
{ |
|||
value = _localValue; |
|||
ValuePriority = BindingPriority.LocalValue; |
|||
} |
|||
|
|||
if (value.HasValue && _coerceValue != null) |
|||
{ |
|||
value = _coerceValue(_owner, value.Value); |
|||
} |
|||
|
|||
if (value != Value) |
|||
{ |
|||
var old = Value; |
|||
Value = value; |
|||
_sink.ValueChanged(Property, ValuePriority, old, value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class AvaloniaPropertyBindingObservable<T> : LightweightObservableBase<BindingValue<T>>, IDescription |
|||
{ |
|||
private readonly WeakReference<IAvaloniaObject> _target; |
|||
private readonly AvaloniaProperty _property; |
|||
private T _value; |
|||
|
|||
#nullable disable |
|||
public AvaloniaPropertyBindingObservable( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty property) |
|||
{ |
|||
_target = new WeakReference<IAvaloniaObject>(target); |
|||
_property = property; |
|||
} |
|||
#nullable enable |
|||
|
|||
public string Description => $"{_target.GetType().Name}.{_property.Name}"; |
|||
|
|||
protected override void Initialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
_value = (T)target.GetValue(_property); |
|||
target.PropertyChanged += PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Deinitialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged -= PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Subscribed(IObserver<BindingValue<T>> observer, bool first) |
|||
{ |
|||
observer.OnNext(new BindingValue<T>(_value)); |
|||
} |
|||
|
|||
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
if (e is AvaloniaPropertyChangedEventArgs<T> typedArgs) |
|||
{ |
|||
var newValue = e.Sender.GetValue(typedArgs.Property); |
|||
|
|||
if (!typedArgs.OldValue.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value)) |
|||
{ |
|||
_value = newValue; |
|||
PublishNext(_value); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var newValue = e.Sender.GetValue(e.Property); |
|||
|
|||
if (!Equals(newValue, _value)) |
|||
{ |
|||
_value = (T)newValue; |
|||
PublishNext(_value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class BindingValueAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>, |
|||
IObserver<T> |
|||
{ |
|||
private readonly IObservable<T> _source; |
|||
private IDisposable? _subscription; |
|||
|
|||
public BindingValueAdapter(IObservable<T> source) => _source = source; |
|||
public void OnCompleted() => PublishCompleted(); |
|||
public void OnError(Exception error) => PublishError(error); |
|||
public void OnNext(T value) => PublishNext(BindingValue<T>.FromUntyped(value)); |
|||
protected override void Subscribed() => _subscription = _source.Subscribe(this); |
|||
protected override void Unsubscribed() => _subscription?.Dispose(); |
|||
} |
|||
|
|||
internal class BindingValueSubjectAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>, |
|||
ISubject<BindingValue<T>> |
|||
{ |
|||
private readonly ISubject<T> _source; |
|||
private readonly Inner _inner; |
|||
private IDisposable? _subscription; |
|||
|
|||
public BindingValueSubjectAdapter(ISubject<T> source) |
|||
{ |
|||
_source = source; |
|||
_inner = new Inner(this); |
|||
} |
|||
|
|||
public void OnCompleted() => _source.OnCompleted(); |
|||
public void OnError(Exception error) => _source.OnError(error); |
|||
|
|||
public void OnNext(BindingValue<T> value) |
|||
{ |
|||
if (value.HasValue) |
|||
{ |
|||
_source.OnNext(value.Value); |
|||
} |
|||
} |
|||
|
|||
protected override void Subscribed() => _subscription = _source.Subscribe(_inner); |
|||
protected override void Unsubscribed() => _subscription?.Dispose(); |
|||
|
|||
private class Inner : IObserver<T> |
|||
{ |
|||
private readonly BindingValueSubjectAdapter<T> _owner; |
|||
|
|||
public Inner(BindingValueSubjectAdapter<T> owner) => _owner = owner; |
|||
|
|||
public void OnCompleted() => _owner.PublishCompleted(); |
|||
public void OnError(Exception error) => _owner.PublishError(error); |
|||
public void OnNext(T value) => _owner.PublishNext(BindingValue<T>.FromUntyped(value)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
public static class BindingValueExtensions |
|||
{ |
|||
public static IObservable<BindingValue<T>> ToBindingValue<T>(this IObservable<T> source) |
|||
{ |
|||
source = source ?? throw new ArgumentNullException(nameof(source)); |
|||
return new BindingValueAdapter<T>(source); |
|||
} |
|||
|
|||
public static ISubject<BindingValue<T>> ToBindingValue<T>(this ISubject<T> source) |
|||
{ |
|||
source = source ?? throw new ArgumentNullException(nameof(source)); |
|||
return new BindingValueSubjectAdapter<T>(source); |
|||
} |
|||
|
|||
public static IObservable<object?> ToUntyped<T>(this IObservable<BindingValue<T>> source) |
|||
{ |
|||
source = source ?? throw new ArgumentNullException(nameof(source)); |
|||
return new UntypedBindingAdapter<T>(source); |
|||
} |
|||
|
|||
public static ISubject<object?> ToUntyped<T>(this ISubject<BindingValue<T>> source) |
|||
{ |
|||
source = source ?? throw new ArgumentNullException(nameof(source)); |
|||
return new UntypedBindingSubjectAdapter<T>(source); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
using Avalonia.Logging; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class TypedBindingAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>, |
|||
IObserver<BindingValue<object>> |
|||
{ |
|||
private readonly IAvaloniaObject _target; |
|||
private readonly AvaloniaProperty<T> _property; |
|||
private readonly IObservable<BindingValue<object>> _source; |
|||
private IDisposable? _subscription; |
|||
|
|||
public TypedBindingAdapter( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty<T> property, |
|||
IObservable<BindingValue<object>> source) |
|||
{ |
|||
_target = target; |
|||
_property = property; |
|||
_source = source; |
|||
} |
|||
|
|||
public void OnNext(BindingValue<object> value) |
|||
{ |
|||
try |
|||
{ |
|||
PublishNext(value.Convert<T>()); |
|||
} |
|||
catch (InvalidCastException e) |
|||
{ |
|||
Logger.TryGet(LogEventLevel.Error)?.Log( |
|||
LogArea.Binding, |
|||
_target, |
|||
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", |
|||
_property.Name, |
|||
_property.PropertyType, |
|||
value.Value, |
|||
value.Value?.GetType()); |
|||
PublishNext(BindingValue<T>.BindingError(e)); |
|||
} |
|||
} |
|||
|
|||
public void OnCompleted() => PublishCompleted(); |
|||
public void OnError(Exception error) => PublishError(error); |
|||
|
|||
public static IObservable<BindingValue<T>> Create( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty<T> property, |
|||
IObservable<BindingValue<object>> source) |
|||
{ |
|||
return source is IObservable<BindingValue<T>> result ? |
|||
result : |
|||
new TypedBindingAdapter<T>(target, property, source); |
|||
} |
|||
|
|||
protected override void Subscribed() => _subscription = _source.Subscribe(this); |
|||
protected override void Unsubscribed() => _subscription?.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Data; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class UntypedBindingAdapter<T> : SingleSubscriberObservableBase<object?>, |
|||
IObserver<BindingValue<T>> |
|||
{ |
|||
private readonly IObservable<BindingValue<T>> _source; |
|||
private IDisposable? _subscription; |
|||
|
|||
public UntypedBindingAdapter(IObservable<BindingValue<T>> source) => _source = source; |
|||
public void OnCompleted() => PublishCompleted(); |
|||
public void OnError(Exception error) => PublishError(error); |
|||
public void OnNext(BindingValue<T> value) => value.ToUntyped(); |
|||
protected override void Subscribed() => _subscription = _source.Subscribe(this); |
|||
protected override void Unsubscribed() => _subscription?.Dispose(); |
|||
} |
|||
|
|||
internal class UntypedBindingSubjectAdapter<T> : SingleSubscriberObservableBase<object?>, |
|||
ISubject<object?> |
|||
{ |
|||
private readonly ISubject<BindingValue<T>> _source; |
|||
private readonly Inner _inner; |
|||
private IDisposable? _subscription; |
|||
|
|||
public UntypedBindingSubjectAdapter(ISubject<BindingValue<T>> source) |
|||
{ |
|||
_source = source; |
|||
_inner = new Inner(this); |
|||
} |
|||
|
|||
public void OnCompleted() => _source.OnCompleted(); |
|||
public void OnError(Exception error) => _source.OnError(error); |
|||
public void OnNext(object? value) |
|||
{ |
|||
_source.OnNext(BindingValue<T>.FromUntyped(value)); |
|||
} |
|||
|
|||
protected override void Subscribed() => _subscription = _source.Subscribe(_inner); |
|||
protected override void Unsubscribed() => _subscription?.Dispose(); |
|||
|
|||
private class Inner : IObserver<BindingValue<T>> |
|||
{ |
|||
private readonly UntypedBindingSubjectAdapter<T> _owner; |
|||
|
|||
public Inner(UntypedBindingSubjectAdapter<T> owner) => _owner = owner; |
|||
|
|||
public void OnCompleted() => _owner.PublishCompleted(); |
|||
public void OnError(Exception error) => _owner.PublishError(error); |
|||
public void OnNext(BindingValue<T> value) => _owner.PublishNext(value.ToUntyped()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,128 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
|
|||
/// Used to fix #855.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
|
|||
internal sealed class DeferredSetter<TSetRecord> |
|||
{ |
|||
private readonly SingleOrQueue<TSetRecord> _pendingValues; |
|||
private bool _isNotifying; |
|||
|
|||
public DeferredSetter() |
|||
{ |
|||
_pendingValues = new SingleOrQueue<TSetRecord>(); |
|||
} |
|||
|
|||
private static void SetAndRaisePropertyChanged(AvaloniaObject source, AvaloniaProperty<TSetRecord> property, ref TSetRecord backing, TSetRecord value) |
|||
{ |
|||
var old = backing; |
|||
|
|||
backing = value; |
|||
|
|||
source.RaisePropertyChanged(property, old, value); |
|||
} |
|||
|
|||
public bool SetAndNotify( |
|||
AvaloniaObject source, |
|||
AvaloniaProperty<TSetRecord> property, |
|||
ref TSetRecord backing, |
|||
TSetRecord value) |
|||
{ |
|||
if (!_isNotifying) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
SetAndRaisePropertyChanged(source, property, ref backing, value); |
|||
} |
|||
|
|||
if (!_pendingValues.Empty) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
while (!_pendingValues.Empty) |
|||
{ |
|||
SetAndRaisePropertyChanged(source, property, ref backing, _pendingValues.Dequeue()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
_pendingValues.Enqueue(value); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool SetAndNotifyCallback<TValue>(AvaloniaProperty property, ISetAndNotifyHandler<TValue> setAndNotifyHandler, ref TValue backing, TValue value) |
|||
where TValue : TSetRecord |
|||
{ |
|||
if (!_isNotifying) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
setAndNotifyHandler.HandleSetAndNotify(property, ref backing, value); |
|||
} |
|||
|
|||
if (!_pendingValues.Empty) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
while (!_pendingValues.Empty) |
|||
{ |
|||
setAndNotifyHandler.HandleSetAndNotify(property, ref backing, (TValue)_pendingValues.Dequeue()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
_pendingValues.Enqueue(value); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposable that marks the property as currently notifying.
|
|||
/// When disposed, marks the property as done notifying.
|
|||
/// </summary>
|
|||
private readonly struct NotifyDisposable : IDisposable |
|||
{ |
|||
private readonly DeferredSetter<TSetRecord> _setter; |
|||
|
|||
internal NotifyDisposable(DeferredSetter<TSetRecord> setter) |
|||
{ |
|||
_setter = setter; |
|||
_setter._isNotifying = true; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_setter._isNotifying = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Handler for set and notify requests.
|
|||
/// </summary>
|
|||
/// <typeparam name="TValue">Value type.</typeparam>
|
|||
internal interface ISetAndNotifyHandler<TValue> |
|||
{ |
|||
/// <summary>
|
|||
/// Handles deferred setter requests to set a value.
|
|||
/// </summary>
|
|||
/// <param name="property">Property being set.</param>
|
|||
/// <param name="backing">Backing field reference.</param>
|
|||
/// <param name="value">New value.</param>
|
|||
void HandleSetAndNotify(AvaloniaProperty property, ref TValue backing, TValue value); |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// A list like struct optimized for holding zero or one items.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of items held in the list.</typeparam>
|
|||
/// <remarks>
|
|||
/// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally.
|
|||
/// </remarks>
|
|||
public ref struct ValueSingleOrList<T> |
|||
{ |
|||
private bool _isSingleSet; |
|||
|
|||
/// <summary>
|
|||
/// Single contained value. Only valid if <see cref="IsSingle"/> is set.
|
|||
/// </summary>
|
|||
public T Single { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// List of values.
|
|||
/// </summary>
|
|||
public List<T> List { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// If this struct is backed by a list.
|
|||
/// </summary>
|
|||
public bool HasList => List != null; |
|||
|
|||
/// <summary>
|
|||
/// If this struct contains only single value and storage was not promoted to a list.
|
|||
/// </summary>
|
|||
public bool IsSingle => List == null && _isSingleSet; |
|||
|
|||
/// <summary>
|
|||
/// Adds a value.
|
|||
/// </summary>
|
|||
/// <param name="value">Value to add.</param>
|
|||
public void Add(T value) |
|||
{ |
|||
if (List != null) |
|||
{ |
|||
List.Add(value); |
|||
} |
|||
else |
|||
{ |
|||
if (!_isSingleSet) |
|||
{ |
|||
Single = value; |
|||
|
|||
_isSingleSet = true; |
|||
} |
|||
else |
|||
{ |
|||
List = new List<T>(); |
|||
|
|||
List.Add(Single); |
|||
List.Add(value); |
|||
|
|||
Single = default; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes a value.
|
|||
/// </summary>
|
|||
/// <param name="value">Value to remove.</param>
|
|||
public bool Remove(T value) |
|||
{ |
|||
if (List != null) |
|||
{ |
|||
return List.Remove(value); |
|||
} |
|||
|
|||
if (!_isSingleSet) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (EqualityComparer<T>.Default.Equals(Single, value)) |
|||
{ |
|||
Single = default; |
|||
|
|||
_isSingleSet = false; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -1,205 +1,271 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Data; |
|||
using Avalonia.PropertyStore; |
|||
using Avalonia.Utilities; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
internal class ValueStore : IPriorityValueOwner |
|||
/// <summary>
|
|||
/// Stores styled property values for an <see cref="AvaloniaObject"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// At its core this class consists of an <see cref="AvaloniaProperty"/> to
|
|||
/// <see cref="IValue"/> mapping which holds the current values for each set property. This
|
|||
/// <see cref="IValue"/> can be in one of 4 states:
|
|||
///
|
|||
/// - For a single local value it will be an instance of <see cref="LocalValueEntry{T}"/>.
|
|||
/// - For a single value of a priority other than LocalValue it will be an instance of
|
|||
/// <see cref="ConstantValueEntry{T}"/>`
|
|||
/// - For a single binding it will be an instance of <see cref="BindingEntry{T}"/>
|
|||
/// - For all other cases it will be an instance of <see cref="PriorityValue{T}"/>
|
|||
/// </remarks>
|
|||
internal class ValueStore : IValueSink |
|||
{ |
|||
private readonly AvaloniaPropertyValueStore<object> _propertyValues; |
|||
private readonly AvaloniaPropertyValueStore<object> _deferredSetters; |
|||
private readonly AvaloniaObject _owner; |
|||
private readonly IValueSink _sink; |
|||
private readonly AvaloniaPropertyValueStore<IValue> _values; |
|||
|
|||
public ValueStore(AvaloniaObject owner) |
|||
{ |
|||
_owner = owner; |
|||
_propertyValues = new AvaloniaPropertyValueStore<object>(); |
|||
_deferredSetters = new AvaloniaPropertyValueStore<object>(); |
|||
_sink = _owner = owner; |
|||
_values = new AvaloniaPropertyValueStore<IValue>(); |
|||
} |
|||
|
|||
public IDisposable AddBinding( |
|||
AvaloniaProperty property, |
|||
IObservable<object> source, |
|||
BindingPriority priority) |
|||
public bool IsAnimating(AvaloniaProperty property) |
|||
{ |
|||
PriorityValue priorityValue; |
|||
|
|||
if (_propertyValues.TryGetValue(property, out var v)) |
|||
{ |
|||
priorityValue = v as PriorityValue; |
|||
|
|||
if (priorityValue == null) |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
|||
_propertyValues.SetValue(property, priorityValue); |
|||
} |
|||
} |
|||
else |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
_propertyValues.AddValue(property, priorityValue); |
|||
return slot.ValuePriority < BindingPriority.LocalValue; |
|||
} |
|||
|
|||
return priorityValue.Add(source, (int)priority); |
|||
return false; |
|||
} |
|||
|
|||
public void AddValue(AvaloniaProperty property, object value, int priority) |
|||
public bool IsSet(AvaloniaProperty property) |
|||
{ |
|||
PriorityValue priorityValue; |
|||
|
|||
if (_propertyValues.TryGetValue(property, out var v)) |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
priorityValue = v as PriorityValue; |
|||
|
|||
if (priorityValue == null) |
|||
{ |
|||
if (priority == (int)BindingPriority.LocalValue) |
|||
{ |
|||
Validate(property, ref value); |
|||
_propertyValues.SetValue(property, value); |
|||
Changed(property, priority, v, value); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
|||
_propertyValues.SetValue(property, priorityValue); |
|||
} |
|||
} |
|||
return slot.Value.HasValue; |
|||
} |
|||
else |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGetValue<T>(StyledPropertyBase<T> property, out T value) |
|||
{ |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
if (value == AvaloniaProperty.UnsetValue) |
|||
{ |
|||
return; |
|||
} |
|||
var v = (IValue<T>)slot; |
|||
|
|||
if (priority == (int)BindingPriority.LocalValue) |
|||
{ |
|||
Validate(property, ref value); |
|||
_propertyValues.AddValue(property, value); |
|||
Changed(property, priority, AvaloniaProperty.UnsetValue, value); |
|||
return; |
|||
} |
|||
else |
|||
if (v.Value.HasValue) |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
_propertyValues.AddValue(property, priorityValue); |
|||
value = v.Value.Value; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
priorityValue.SetValue(value, priority); |
|||
value = default!; |
|||
return false; |
|||
} |
|||
|
|||
public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) |
|||
public void SetValue<T>(StyledPropertyBase<T> property, T value, BindingPriority priority) |
|||
{ |
|||
_owner.BindingNotificationReceived(property, notification); |
|||
if (property.ValidateValue?.Invoke(value) == false) |
|||
{ |
|||
throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); |
|||
} |
|||
|
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
SetExisting(slot, property, value, priority); |
|||
} |
|||
else if (property.HasCoercion) |
|||
{ |
|||
// If the property has any coercion callbacks then always create a PriorityValue.
|
|||
var entry = new PriorityValue<T>(_owner, property, this); |
|||
_values.AddValue(property, entry); |
|||
entry.SetValue(value, priority); |
|||
} |
|||
else if (priority == BindingPriority.LocalValue) |
|||
{ |
|||
_values.AddValue(property, new LocalValueEntry<T>(value)); |
|||
_sink.ValueChanged(property, priority, default, value); |
|||
} |
|||
else |
|||
{ |
|||
var entry = new ConstantValueEntry<T>(property, value, priority); |
|||
_values.AddValue(property, entry); |
|||
_sink.ValueChanged(property, priority, default, value); |
|||
} |
|||
} |
|||
|
|||
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) |
|||
public IDisposable AddBinding<T>( |
|||
StyledPropertyBase<T> property, |
|||
IObservable<BindingValue<T>> source, |
|||
BindingPriority priority) |
|||
{ |
|||
_owner.PriorityValueChanged(property, priority, oldValue, newValue); |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
return BindExisting(slot, property, source, priority); |
|||
} |
|||
else if (property.HasCoercion) |
|||
{ |
|||
// If the property has any coercion callbacks then always create a PriorityValue.
|
|||
var entry = new PriorityValue<T>(_owner, property, this); |
|||
var binding = entry.AddBinding(source, priority); |
|||
_values.AddValue(property, entry); |
|||
binding.Start(); |
|||
return binding; |
|||
} |
|||
else |
|||
{ |
|||
var entry = new BindingEntry<T>(_owner, property, source, priority, this); |
|||
_values.AddValue(property, entry); |
|||
entry.Start(); |
|||
return entry; |
|||
} |
|||
} |
|||
|
|||
public IDictionary<AvaloniaProperty, object> GetSetValues() |
|||
public void ClearLocalValue<T>(StyledPropertyBase<T> property) |
|||
{ |
|||
return _propertyValues.ToDictionary(); |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
if (slot is PriorityValue<T> p) |
|||
{ |
|||
p.ClearLocalValue(); |
|||
} |
|||
else |
|||
{ |
|||
var remove = slot is ConstantValueEntry<T> c ? |
|||
c.Priority == BindingPriority.LocalValue : |
|||
!(slot is IPriorityValueEntry<T>); |
|||
|
|||
if (remove) |
|||
{ |
|||
var old = TryGetValue(property, out var value) ? value : default; |
|||
_values.Remove(property); |
|||
_sink.ValueChanged( |
|||
property, |
|||
BindingPriority.LocalValue, |
|||
old, |
|||
BindingValue<T>.Unset); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void LogError(AvaloniaProperty property, Exception e) |
|||
public void CoerceValue<T>(StyledPropertyBase<T> property) |
|||
{ |
|||
_owner.LogBindingError(property, e); |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
if (slot is PriorityValue<T> p) |
|||
{ |
|||
p.CoerceValue(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public object GetValue(AvaloniaProperty property) |
|||
public Diagnostics.AvaloniaPropertyValue? GetDiagnostic(AvaloniaProperty property) |
|||
{ |
|||
var result = AvaloniaProperty.UnsetValue; |
|||
|
|||
if (_propertyValues.TryGetValue(property, out var value)) |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value; |
|||
return new Diagnostics.AvaloniaPropertyValue( |
|||
property, |
|||
slot.Value.HasValue ? (object)slot.Value : AvaloniaProperty.UnsetValue, |
|||
slot.ValuePriority, |
|||
null); |
|||
} |
|||
|
|||
return result; |
|||
return null; |
|||
} |
|||
|
|||
public bool IsAnimating(AvaloniaProperty property) |
|||
void IValueSink.ValueChanged<T>( |
|||
StyledPropertyBase<T> property, |
|||
BindingPriority priority, |
|||
Optional<T> oldValue, |
|||
BindingValue<T> newValue) |
|||
{ |
|||
return _propertyValues.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating; |
|||
_sink.ValueChanged(property, priority, oldValue, newValue); |
|||
} |
|||
|
|||
public bool IsSet(AvaloniaProperty property) |
|||
void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) |
|||
{ |
|||
if (_propertyValues.TryGetValue(property, out var value)) |
|||
if (_values.TryGetValue(property, out var slot)) |
|||
{ |
|||
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue; |
|||
if (slot == entry) |
|||
{ |
|||
_values.Remove(property); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void Revalidate(AvaloniaProperty property) |
|||
private void SetExisting<T>( |
|||
object slot, |
|||
StyledPropertyBase<T> property, |
|||
T value, |
|||
BindingPriority priority) |
|||
{ |
|||
if (_propertyValues.TryGetValue(property, out var value)) |
|||
if (slot is IPriorityValueEntry<T> e) |
|||
{ |
|||
(value as PriorityValue)?.Revalidate(); |
|||
var priorityValue = new PriorityValue<T>(_owner, property, this, e); |
|||
_values.SetValue(property, priorityValue); |
|||
priorityValue.SetValue(value, priority); |
|||
} |
|||
} |
|||
|
|||
public void VerifyAccess() => _owner.VerifyAccess(); |
|||
|
|||
private PriorityValue CreatePriorityValue(AvaloniaProperty property) |
|||
{ |
|||
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
|||
Func<object, object> validate2 = null; |
|||
|
|||
if (validate != null) |
|||
else if (slot is PriorityValue<T> p) |
|||
{ |
|||
validate2 = v => validate(_owner, v); |
|||
p.SetValue(value, priority); |
|||
} |
|||
else if (slot is LocalValueEntry<T> l) |
|||
{ |
|||
if (priority == BindingPriority.LocalValue) |
|||
{ |
|||
var old = l.Value; |
|||
l.SetValue(value); |
|||
_sink.ValueChanged(property, priority, old, value); |
|||
} |
|||
else |
|||
{ |
|||
var priorityValue = new PriorityValue<T>(_owner, property, this, l); |
|||
_values.SetValue(property, priorityValue); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("Unrecognised value store slot type."); |
|||
} |
|||
|
|||
return new PriorityValue( |
|||
this, |
|||
property, |
|||
property.PropertyType, |
|||
validate2); |
|||
} |
|||
|
|||
private void Validate(AvaloniaProperty property, ref object value) |
|||
private IDisposable BindExisting<T>( |
|||
object slot, |
|||
StyledPropertyBase<T> property, |
|||
IObservable<BindingValue<T>> source, |
|||
BindingPriority priority) |
|||
{ |
|||
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
|||
PriorityValue<T> priorityValue; |
|||
|
|||
if (validate != null && value != AvaloniaProperty.UnsetValue) |
|||
if (slot is IPriorityValueEntry<T> e) |
|||
{ |
|||
value = validate(_owner, value); |
|||
priorityValue = new PriorityValue<T>(_owner, property, this, e); |
|||
} |
|||
} |
|||
|
|||
private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property) |
|||
{ |
|||
if (_deferredSetters.TryGetValue(property, out var deferredSetter)) |
|||
else if (slot is PriorityValue<T> p) |
|||
{ |
|||
return (DeferredSetter<T>)deferredSetter; |
|||
priorityValue = p; |
|||
} |
|||
else if (slot is LocalValueEntry<T> l) |
|||
{ |
|||
priorityValue = new PriorityValue<T>(_owner, property, this, l); |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("Unrecognised value store slot type."); |
|||
} |
|||
|
|||
var newDeferredSetter = new DeferredSetter<T>(); |
|||
|
|||
_deferredSetters.AddValue(property, newDeferredSetter); |
|||
|
|||
return newDeferredSetter; |
|||
} |
|||
|
|||
public DeferredSetter<object> GetNonDirectDeferredSetter(AvaloniaProperty property) |
|||
{ |
|||
return GetDeferredSetter<object>(property); |
|||
} |
|||
|
|||
public DeferredSetter<T> GetDirectDeferredSetter<T>(AvaloniaProperty<T> property) |
|||
{ |
|||
return GetDeferredSetter<T>(property); |
|||
var binding = priorityValue.AddBinding(source, priority); |
|||
_values.SetValue(property, priorityValue); |
|||
binding.Start(); |
|||
return binding; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue