committed by
GitHub
215 changed files with 10058 additions and 2992 deletions
@ -1,45 +1,52 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" |
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
x:Class="ControlCatalog.Pages.ImagePage"> |
x:Class="ControlCatalog.Pages.ImagePage"> |
||||
<StackPanel Orientation="Vertical" Spacing="4"> |
<DockPanel> |
||||
<TextBlock Classes="h1">Image</TextBlock> |
<StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4"> |
||||
<TextBlock Classes="h2">Displays an image</TextBlock> |
<TextBlock Classes="h1">Image</TextBlock> |
||||
|
<TextBlock Classes="h2">Displays an image</TextBlock> |
||||
<StackPanel Orientation="Horizontal" |
</StackPanel> |
||||
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> |
|
||||
|
|
||||
<StackPanel Orientation="Vertical"> |
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64"> |
||||
<TextBlock>Uniform</TextBlock> |
|
||||
<Image Source="/Assets/delicate-arch-896885_640.jpg" |
<DockPanel Grid.Column="0" Grid.Row="1" Margin="16"> |
||||
Width="100" Height="200" |
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock> |
||||
Stretch="Uniform"/> |
<ComboBox Name="bitmapStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapStretchChanged"> |
||||
</StackPanel> |
<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"> |
<DockPanel Grid.Column="1" Grid.Row="1" Margin="16"> |
||||
<TextBlock>UniformToFill</TextBlock> |
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock> |
||||
<Image Source="/Assets/delicate-arch-896885_640.jpg" |
<ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged"> |
||||
Width="100" Height="200" |
<ComboBoxItem>None</ComboBoxItem> |
||||
Stretch="UniformToFill"/> |
<ComboBoxItem>Fill</ComboBoxItem> |
||||
</StackPanel> |
<ComboBoxItem>Uniform</ComboBoxItem> |
||||
</StackPanel> |
<ComboBoxItem>UniformToFill</ComboBoxItem> |
||||
<StackPanel Orientation="Vertical"> |
</ComboBox> |
||||
<TextBlock>Window Icon as an Image</TextBlock> |
<Image Name="drawingImage"> |
||||
<Image Name="Icon" Width="100" Height="200" Stretch="None" /> |
<Image.Source> |
||||
</StackPanel> |
<DrawingImage> |
||||
</StackPanel> |
<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> |
</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; |
||||
using System.Collections.Generic; |
|
||||
using Avalonia.Data; |
using Avalonia.Data; |
||||
|
using Avalonia.PropertyStore; |
||||
using Avalonia.Utilities; |
using Avalonia.Utilities; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
namespace Avalonia |
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 AvaloniaObject _owner; |
||||
|
private readonly IValueSink _sink; |
||||
|
private readonly AvaloniaPropertyValueStore<IValue> _values; |
||||
|
|
||||
public ValueStore(AvaloniaObject owner) |
public ValueStore(AvaloniaObject owner) |
||||
{ |
{ |
||||
_owner = owner; |
_sink = _owner = owner; |
||||
_propertyValues = new AvaloniaPropertyValueStore<object>(); |
_values = new AvaloniaPropertyValueStore<IValue>(); |
||||
_deferredSetters = new AvaloniaPropertyValueStore<object>(); |
|
||||
} |
} |
||||
|
|
||||
public IDisposable AddBinding( |
public bool IsAnimating(AvaloniaProperty property) |
||||
AvaloniaProperty property, |
|
||||
IObservable<object> source, |
|
||||
BindingPriority priority) |
|
||||
{ |
{ |
||||
PriorityValue priorityValue; |
if (_values.TryGetValue(property, out var slot)) |
||||
|
|
||||
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 |
|
||||
{ |
{ |
||||
priorityValue = CreatePriorityValue(property); |
return slot.ValuePriority < BindingPriority.LocalValue; |
||||
_propertyValues.AddValue(property, priorityValue); |
|
||||
} |
} |
||||
|
|
||||
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 (_values.TryGetValue(property, out var slot)) |
||||
|
|
||||
if (_propertyValues.TryGetValue(property, out var v)) |
|
||||
{ |
{ |
||||
priorityValue = v as PriorityValue; |
return slot.Value.HasValue; |
||||
|
|
||||
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); |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
else |
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public bool TryGetValue<T>(StyledPropertyBase<T> property, out T value) |
||||
|
{ |
||||
|
if (_values.TryGetValue(property, out var slot)) |
||||
{ |
{ |
||||
if (value == AvaloniaProperty.UnsetValue) |
var v = (IValue<T>)slot; |
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (priority == (int)BindingPriority.LocalValue) |
if (v.Value.HasValue) |
||||
{ |
|
||||
Validate(property, ref value); |
|
||||
_propertyValues.AddValue(property, value); |
|
||||
Changed(property, priority, AvaloniaProperty.UnsetValue, value); |
|
||||
return; |
|
||||
} |
|
||||
else |
|
||||
{ |
{ |
||||
priorityValue = CreatePriorityValue(property); |
value = v.Value.Value; |
||||
_propertyValues.AddValue(property, priorityValue); |
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 (_values.TryGetValue(property, out var slot)) |
||||
|
|
||||
if (_propertyValues.TryGetValue(property, out var value)) |
|
||||
{ |
{ |
||||
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); |
||||
} |
} |
||||
} |
else if (slot is PriorityValue<T> p) |
||||
|
|
||||
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) |
|
||||
{ |
{ |
||||
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); |
||||
} |
} |
||||
} |
else if (slot is PriorityValue<T> p) |
||||
|
|
||||
private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property) |
|
||||
{ |
|
||||
if (_deferredSetters.TryGetValue(property, out var deferredSetter)) |
|
||||
{ |
{ |
||||
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>(); |
var binding = priorityValue.AddBinding(source, priority); |
||||
|
_values.SetValue(property, priorityValue); |
||||
_deferredSetters.AddValue(property, newDeferredSetter); |
binding.Start(); |
||||
|
return binding; |
||||
return newDeferredSetter; |
|
||||
} |
|
||||
|
|
||||
public DeferredSetter<object> GetNonDirectDeferredSetter(AvaloniaProperty property) |
|
||||
{ |
|
||||
return GetDeferredSetter<object>(property); |
|
||||
} |
|
||||
|
|
||||
public DeferredSetter<T> GetDirectDeferredSetter<T>(AvaloniaProperty<T> property) |
|
||||
{ |
|
||||
return GetDeferredSetter<T>(property); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue