committed by
GitHub
335 changed files with 14990 additions and 8535 deletions
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<InstrumentOutputAssembly>False</InstrumentOutputAssembly> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<InstrumentOutputAssembly>False</InstrumentOutputAssembly> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<InstrumentOutputAssembly>False</InstrumentOutputAssembly> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<InstrumentOutputAssembly>False</InstrumentOutputAssembly> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -1,7 +1,7 @@ |
|||||
using Avalonia; |
using Avalonia; |
||||
using Avalonia.Web.Blazor; |
using Avalonia.Browser.Blazor; |
||||
|
|
||||
namespace ControlCatalog.Blazor.Web; |
namespace ControlCatalog.Browser.Blazor; |
||||
|
|
||||
public partial class App |
public partial class App |
||||
{ |
{ |
||||
@ -1,5 +1,5 @@ |
|||||
@page "/" |
@page "/" |
||||
|
|
||||
@using Avalonia.Web.Blazor |
@using Avalonia.Browser.Blazor |
||||
|
|
||||
<AvaloniaView /> |
<AvaloniaView /> |
||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
@ -1,11 +1,11 @@ |
|||||
using System; |
using System; |
||||
using System.Runtime.InteropServices.JavaScript; |
using System.Runtime.InteropServices.JavaScript; |
||||
using Avalonia.Platform; |
using Avalonia.Platform; |
||||
using Avalonia.Web; |
using Avalonia.Browser; |
||||
|
|
||||
using ControlCatalog.Pages; |
using ControlCatalog.Pages; |
||||
|
|
||||
namespace ControlCatalog.Web; |
namespace ControlCatalog.Browser; |
||||
|
|
||||
public class EmbedSampleWeb : INativeDemoControl |
public class EmbedSampleWeb : INativeDemoControl |
||||
{ |
{ |
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -1,8 +1,8 @@ |
|||||
using System.Runtime.Versioning; |
using System.Runtime.Versioning; |
||||
using Avalonia; |
using Avalonia; |
||||
using Avalonia.Web; |
using Avalonia.Browser; |
||||
using ControlCatalog; |
using ControlCatalog; |
||||
using ControlCatalog.Web; |
using ControlCatalog.Browser; |
||||
|
|
||||
[assembly:SupportedOSPlatform("browser")] |
[assembly:SupportedOSPlatform("browser")] |
||||
|
|
||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
@ -0,0 +1,32 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
|
||||
|
namespace System; |
||||
|
|
||||
|
#if !NET6_0_OR_GREATER
|
||||
|
internal static class CollectionCompatibilityExtensions |
||||
|
{ |
||||
|
public static bool Remove<TKey, TValue>( |
||||
|
this Dictionary<TKey, TValue> o, |
||||
|
TKey key, |
||||
|
[MaybeNullWhen(false)] out TValue value) |
||||
|
where TKey : notnull |
||||
|
{ |
||||
|
if (o.TryGetValue(key, out value)) |
||||
|
return o.Remove(key); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> o, TKey key, TValue value) |
||||
|
where TKey : notnull |
||||
|
{ |
||||
|
if (!o.ContainsKey(key)) |
||||
|
{ |
||||
|
o.Add(key, value); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal static class AvaloniaPropertyDictionaryPool<TValue> |
||||
|
{ |
||||
|
private const int MaxPoolSize = 4; |
||||
|
private static readonly Stack<AvaloniaPropertyDictionary<TValue>> _pool = new(); |
||||
|
|
||||
|
public static AvaloniaPropertyDictionary<TValue> Get() |
||||
|
{ |
||||
|
return _pool.Count == 0 ? new() : _pool.Pop(); |
||||
|
} |
||||
|
|
||||
|
public static void Release(AvaloniaPropertyDictionary<TValue> dictionary) |
||||
|
{ |
||||
|
if (_pool.Count < MaxPoolSize) |
||||
|
{ |
||||
|
dictionary.Clear(); |
||||
|
_pool.Push(dictionary); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,154 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Data; |
|
||||
using Avalonia.Threading; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
|
|
||||
/// </summary>
|
|
||||
internal interface IBindingEntry : IBatchUpdate, IPriorityValueEntry, IDisposable |
|
||||
{ |
|
||||
void Start(bool ignoreBatchUpdate); |
|
||||
} |
|
||||
|
|
||||
/// <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 AvaloniaObject _owner; |
|
||||
private ValueOwner<T> _sink; |
|
||||
private IDisposable? _subscription; |
|
||||
private bool _isSubscribed; |
|
||||
private bool _batchUpdate; |
|
||||
private Optional<T> _value; |
|
||||
|
|
||||
public BindingEntry( |
|
||||
AvaloniaObject owner, |
|
||||
StyledPropertyBase<T> property, |
|
||||
IObservable<BindingValue<T>> source, |
|
||||
BindingPriority priority, |
|
||||
ValueOwner<T> sink) |
|
||||
{ |
|
||||
_owner = owner; |
|
||||
Property = property; |
|
||||
Source = source; |
|
||||
Priority = priority; |
|
||||
_sink = sink; |
|
||||
} |
|
||||
|
|
||||
public StyledPropertyBase<T> Property { get; } |
|
||||
public BindingPriority Priority { get; private set; } |
|
||||
public IObservable<BindingValue<T>> Source { get; } |
|
||||
Optional<object?> IValue.GetValue() => _value.ToObject(); |
|
||||
|
|
||||
public void BeginBatchUpdate() => _batchUpdate = true; |
|
||||
|
|
||||
public void EndBatchUpdate() |
|
||||
{ |
|
||||
_batchUpdate = false; |
|
||||
|
|
||||
if (_sink.IsValueStore) |
|
||||
Start(); |
|
||||
} |
|
||||
|
|
||||
public Optional<T> GetValue(BindingPriority maxPriority) |
|
||||
{ |
|
||||
return Priority >= maxPriority ? _value : Optional<T>.Empty; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_subscription?.Dispose(); |
|
||||
_subscription = null; |
|
||||
OnCompleted(); |
|
||||
} |
|
||||
|
|
||||
public void OnCompleted() |
|
||||
{ |
|
||||
var oldValue = _value; |
|
||||
_value = default; |
|
||||
Priority = BindingPriority.Unset; |
|
||||
_isSubscribed = false; |
|
||||
_sink.Completed(Property, this, oldValue); |
|
||||
} |
|
||||
|
|
||||
public void OnError(Exception error) |
|
||||
{ |
|
||||
throw new NotImplementedException("BindingEntry.OnError is not implemented", error); |
|
||||
} |
|
||||
|
|
||||
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() => Start(false); |
|
||||
|
|
||||
public void Start(bool ignoreBatchUpdate) |
|
||||
{ |
|
||||
// We can't use _subscription to check whether we're subscribed because it won't be set
|
|
||||
// until Subscribe has finished, which will be too late to prevent reentrancy. In addition
|
|
||||
// don't re-subscribe to completed/disposed bindings (indicated by Unset priority).
|
|
||||
if (!_isSubscribed && |
|
||||
Priority != BindingPriority.Unset && |
|
||||
(!_batchUpdate || ignoreBatchUpdate)) |
|
||||
{ |
|
||||
_isSubscribed = true; |
|
||||
_subscription = Source.Subscribe(this); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Reparent(PriorityValue<T> parent) => _sink = new(parent); |
|
||||
|
|
||||
public void RaiseValueChanged( |
|
||||
AvaloniaObject owner, |
|
||||
AvaloniaProperty property, |
|
||||
Optional<object?> oldValue, |
|
||||
Optional<object?> newValue) |
|
||||
{ |
|
||||
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
owner, |
|
||||
(AvaloniaProperty<T>)property, |
|
||||
oldValue.Cast<T>(), |
|
||||
newValue.Cast<T>(), |
|
||||
Priority)); |
|
||||
} |
|
||||
|
|
||||
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(new AvaloniaPropertyChangedEventArgs<T>(_owner, Property, old, value, Priority)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,148 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reactive.Disposables; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal abstract class BindingEntryBase<TValue, TSource> : IValueEntry<TValue>, |
||||
|
IObserver<TSource>, |
||||
|
IObserver<BindingValue<TSource>>, |
||||
|
IDisposable |
||||
|
{ |
||||
|
private static readonly IDisposable s_creating = Disposable.Empty; |
||||
|
private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { }); |
||||
|
private IDisposable? _subscription; |
||||
|
private bool _hasValue; |
||||
|
private TValue? _value; |
||||
|
|
||||
|
protected BindingEntryBase( |
||||
|
ValueFrame frame, |
||||
|
AvaloniaProperty property, |
||||
|
IObservable<BindingValue<TSource>> source) |
||||
|
{ |
||||
|
Frame = frame; |
||||
|
Source = source; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
protected BindingEntryBase( |
||||
|
ValueFrame frame, |
||||
|
AvaloniaProperty property, |
||||
|
IObservable<TSource> source) |
||||
|
{ |
||||
|
Frame = frame; |
||||
|
Source = source; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
public bool HasValue |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
Start(produceValue: false); |
||||
|
return _hasValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool IsSubscribed => _subscription is not null; |
||||
|
public AvaloniaProperty Property { get; } |
||||
|
AvaloniaProperty IValueEntry.Property => Property; |
||||
|
protected ValueFrame Frame { get; } |
||||
|
protected object Source { get; } |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Unsubscribe(); |
||||
|
BindingCompleted(); |
||||
|
} |
||||
|
|
||||
|
public TValue GetValue() |
||||
|
{ |
||||
|
Start(produceValue: false); |
||||
|
if (!_hasValue) |
||||
|
throw new AvaloniaInternalException("The binding entry has no value."); |
||||
|
return _value!; |
||||
|
} |
||||
|
|
||||
|
public void Start() => Start(true); |
||||
|
|
||||
|
public void OnCompleted() => BindingCompleted(); |
||||
|
public void OnError(Exception error) => BindingCompleted(); |
||||
|
public void OnNext(TSource value) => SetValue(ConvertAndValidate(value)); |
||||
|
public void OnNext(BindingValue<TSource> value) => SetValue(ConvertAndValidate(value)); |
||||
|
|
||||
|
public virtual void Unsubscribe() |
||||
|
{ |
||||
|
_subscription?.Dispose(); |
||||
|
_subscription = null; |
||||
|
} |
||||
|
|
||||
|
object? IValueEntry.GetValue() |
||||
|
{ |
||||
|
Start(produceValue: false); |
||||
|
if (!_hasValue) |
||||
|
throw new AvaloniaInternalException("The BindingEntry<T> has no value."); |
||||
|
return _value!; |
||||
|
} |
||||
|
|
||||
|
protected abstract BindingValue<TValue> ConvertAndValidate(TSource value); |
||||
|
protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value); |
||||
|
|
||||
|
protected virtual void Start(bool produceValue) |
||||
|
{ |
||||
|
if (_subscription is not null) |
||||
|
return; |
||||
|
|
||||
|
_subscription = produceValue ? s_creating : s_creatingQuiet; |
||||
|
_subscription = Source switch |
||||
|
{ |
||||
|
IObservable<BindingValue<TSource>> bv => bv.Subscribe(this), |
||||
|
IObservable<TSource> b => b.Subscribe(this), |
||||
|
_ => throw new AvaloniaInternalException("Unexpected binding source."), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private void ClearValue() |
||||
|
{ |
||||
|
if (_hasValue) |
||||
|
{ |
||||
|
_hasValue = false; |
||||
|
_value = default; |
||||
|
if (_subscription is not null) |
||||
|
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SetValue(BindingValue<TValue> value) |
||||
|
{ |
||||
|
if (Frame.Owner is null) |
||||
|
return; |
||||
|
|
||||
|
LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value); |
||||
|
|
||||
|
if (value.HasValue) |
||||
|
{ |
||||
|
if (!_hasValue || !EqualityComparer<TValue>.Default.Equals(_value, value.Value)) |
||||
|
{ |
||||
|
_value = value.Value; |
||||
|
_hasValue = true; |
||||
|
if (_subscription is not null && _subscription != s_creatingQuiet) |
||||
|
Frame.Owner?.OnBindingValueChanged(this, Frame.Priority); |
||||
|
} |
||||
|
} |
||||
|
else if (value.Type != BindingValueType.DoNothing) |
||||
|
{ |
||||
|
ClearValue(); |
||||
|
if (_subscription is not null && _subscription != s_creatingQuiet) |
||||
|
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void BindingCompleted() |
||||
|
{ |
||||
|
_subscription = null; |
||||
|
Frame.OnBindingCompleted(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,82 +0,0 @@ |
|||||
using System; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents an untyped interface to <see cref="ConstantValueEntry{T}"/>.
|
|
||||
/// </summary>
|
|
||||
internal interface IConstantValueEntry : IPriorityValueEntry, IDisposable |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <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>, IConstantValueEntry |
|
||||
{ |
|
||||
private ValueOwner<T> _sink; |
|
||||
private Optional<T> _value; |
|
||||
|
|
||||
public ConstantValueEntry( |
|
||||
StyledPropertyBase<T> property, |
|
||||
T value, |
|
||||
BindingPriority priority, |
|
||||
ValueOwner<T> sink) |
|
||||
{ |
|
||||
Property = property; |
|
||||
_value = value; |
|
||||
Priority = priority; |
|
||||
_sink = sink; |
|
||||
} |
|
||||
|
|
||||
public ConstantValueEntry( |
|
||||
StyledPropertyBase<T> property, |
|
||||
Optional<T> value, |
|
||||
BindingPriority priority, |
|
||||
ValueOwner<T> sink) |
|
||||
{ |
|
||||
Property = property; |
|
||||
_value = value; |
|
||||
Priority = priority; |
|
||||
_sink = sink; |
|
||||
} |
|
||||
|
|
||||
public StyledPropertyBase<T> Property { get; } |
|
||||
public BindingPriority Priority { get; private set; } |
|
||||
Optional<object?> IValue.GetValue() => _value.ToObject(); |
|
||||
|
|
||||
public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation) |
|
||||
{ |
|
||||
return Priority >= maxPriority ? _value : Optional<T>.Empty; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
var oldValue = _value; |
|
||||
_value = default; |
|
||||
Priority = BindingPriority.Unset; |
|
||||
_sink.Completed(Property, this, oldValue); |
|
||||
} |
|
||||
|
|
||||
public void Reparent(PriorityValue<T> sink) => _sink = new(sink); |
|
||||
public void Start() { } |
|
||||
|
|
||||
public void RaiseValueChanged( |
|
||||
AvaloniaObject owner, |
|
||||
AvaloniaProperty property, |
|
||||
Optional<object?> oldValue, |
|
||||
Optional<object?> newValue) |
|
||||
{ |
|
||||
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
owner, |
|
||||
(AvaloniaProperty<T>)property, |
|
||||
oldValue.Cast<T>(), |
|
||||
newValue.Cast<T>(), |
|
||||
Priority)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,76 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal class DirectBindingObserver<T> : IObserver<T>, |
||||
|
IObserver<BindingValue<T>>, |
||||
|
IDisposable |
||||
|
{ |
||||
|
private readonly ValueStore _owner; |
||||
|
private IDisposable? _subscription; |
||||
|
|
||||
|
public DirectBindingObserver(ValueStore owner, DirectPropertyBase<T> property) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
public DirectPropertyBase<T> Property { get;} |
||||
|
|
||||
|
public void Start(IObservable<T> source) |
||||
|
{ |
||||
|
_subscription = source.Subscribe(this); |
||||
|
} |
||||
|
|
||||
|
public void Start(IObservable<BindingValue<T>> source) |
||||
|
{ |
||||
|
_subscription = source.Subscribe(this); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_subscription?.Dispose(); |
||||
|
_subscription = null; |
||||
|
_owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
} |
||||
|
|
||||
|
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
public void OnError(Exception error) => OnCompleted(); |
||||
|
|
||||
|
public void OnNext(T value) |
||||
|
{ |
||||
|
if (Dispatcher.UIThread.CheckAccess()) |
||||
|
{ |
||||
|
_owner.Owner.SetDirectValueUnchecked<T>(Property, 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 = _owner.Owner; |
||||
|
var property = Property; |
||||
|
var newValue = value; |
||||
|
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void OnNext(BindingValue<T> value) |
||||
|
{ |
||||
|
if (Dispatcher.UIThread.CheckAccess()) |
||||
|
{ |
||||
|
_owner.Owner.SetDirectValueUnchecked<T>(Property, 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 = _owner.Owner; |
||||
|
var property = Property; |
||||
|
var newValue = value; |
||||
|
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal class DirectUntypedBindingObserver<T> : IObserver<object?>, |
||||
|
IDisposable |
||||
|
{ |
||||
|
private readonly ValueStore _owner; |
||||
|
private IDisposable? _subscription; |
||||
|
|
||||
|
public DirectUntypedBindingObserver(ValueStore owner, DirectPropertyBase<T> property) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
public DirectPropertyBase<T> Property { get;} |
||||
|
|
||||
|
public void Start(IObservable<object?> source) |
||||
|
{ |
||||
|
_subscription = source.Subscribe(this); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_subscription?.Dispose(); |
||||
|
_subscription = null; |
||||
|
_owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
} |
||||
|
|
||||
|
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
public void OnError(Exception error) => OnCompleted(); |
||||
|
|
||||
|
public void OnNext(object? value) |
||||
|
{ |
||||
|
var typed = BindingValue<T>.FromUntyped(value); |
||||
|
|
||||
|
if (Dispatcher.UIThread.CheckAccess()) |
||||
|
{ |
||||
|
_owner.Owner.SetDirectValueUnchecked<T>(Property, typed); |
||||
|
} |
||||
|
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 = _owner.Owner; |
||||
|
var property = Property; |
||||
|
var newValue = value; |
||||
|
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, typed)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,168 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents the active value for a property in a <see cref="ValueStore"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This class is an abstract base for the generic <see cref="EffectiveValue{T}"/>.
|
||||
|
/// </remarks>
|
||||
|
internal abstract class EffectiveValue |
||||
|
{ |
||||
|
private IValueEntry? _valueEntry; |
||||
|
private IValueEntry? _baseValueEntry; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current effective value as a boxed value.
|
||||
|
/// </summary>
|
||||
|
public object? Value => GetBoxedValue(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the priority of the current effective value.
|
||||
|
/// </summary>
|
||||
|
public BindingPriority Priority { get; protected set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the priority of the current base value.
|
||||
|
/// </summary>
|
||||
|
public BindingPriority BasePriority { get; protected set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Begins a reevaluation pass on the effective value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="clearLocalValue">
|
||||
|
/// Determines whether any current local value should be cleared.
|
||||
|
/// </param>
|
||||
|
/// <remarks>
|
||||
|
/// This method resets the <see cref="Priority"/> and <see cref="BasePriority"/> properties
|
||||
|
/// to Unset, pending reevaluation.
|
||||
|
/// </remarks>
|
||||
|
public void BeginReevaluation(bool clearLocalValue = false) |
||||
|
{ |
||||
|
if (clearLocalValue || Priority != BindingPriority.LocalValue) |
||||
|
Priority = BindingPriority.Unset; |
||||
|
if (clearLocalValue || BasePriority != BindingPriority.LocalValue) |
||||
|
BasePriority = BindingPriority.Unset; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ends a reevaluation pass on the effective value.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This method unsubscribes from any unused value entries.
|
||||
|
/// </remarks>
|
||||
|
public void EndReevaluation() |
||||
|
{ |
||||
|
if (Priority == BindingPriority.Unset) |
||||
|
{ |
||||
|
_valueEntry?.Unsubscribe(); |
||||
|
_valueEntry = null; |
||||
|
} |
||||
|
|
||||
|
if (BasePriority == BindingPriority.Unset) |
||||
|
{ |
||||
|
_baseValueEntry?.Unsubscribe(); |
||||
|
_baseValueEntry = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the value and base value for a non-LocalValue priority, raising
|
||||
|
/// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
|
||||
|
/// </summary>
|
||||
|
/// <param name="owner">The associated value store.</param>
|
||||
|
/// <param name="value">The new value of the property.</param>
|
||||
|
/// <param name="priority">The priority of the new value.</param>
|
||||
|
public abstract void SetAndRaise( |
||||
|
ValueStore owner, |
||||
|
IValueEntry value, |
||||
|
BindingPriority priority); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raises <see cref="AvaloniaObject.PropertyChanged"/> in response to an inherited value
|
||||
|
/// change.
|
||||
|
/// </summary>
|
||||
|
/// <param name="owner">The owner object.</param>
|
||||
|
/// <param name="property">The property being changed.</param>
|
||||
|
/// <param name="oldValue">The old value of the property.</param>
|
||||
|
/// <param name="newValue">The new value of the property.</param>
|
||||
|
public abstract void RaiseInheritedValueChanged( |
||||
|
AvaloniaObject owner, |
||||
|
AvaloniaProperty property, |
||||
|
EffectiveValue? oldValue, |
||||
|
EffectiveValue? newValue); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Removes the current animation value and reverts to the base value, raising
|
||||
|
/// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
|
||||
|
/// </summary>
|
||||
|
/// <param name="owner">The associated value store.</param>
|
||||
|
/// <param name="property">The property being changed.</param>
|
||||
|
public abstract void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Coerces the property value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="owner">The associated value store.</param>
|
||||
|
/// <param name="property">The property to coerce.</param>
|
||||
|
public abstract void CoerceValue(ValueStore owner, AvaloniaProperty property); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disposes the effective value, raising <see cref="AvaloniaObject.PropertyChanged"/>
|
||||
|
/// where necessary.
|
||||
|
/// </summary>
|
||||
|
/// <param name="owner">The associated value store.</param>
|
||||
|
/// <param name="property">The property being cleared.</param>
|
||||
|
public abstract void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property); |
||||
|
|
||||
|
protected abstract object? GetBoxedValue(); |
||||
|
|
||||
|
protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority) |
||||
|
{ |
||||
|
Debug.Assert(priority != BindingPriority.LocalValue); |
||||
|
|
||||
|
if (priority <= BindingPriority.Animation) |
||||
|
{ |
||||
|
// If we've received an animation value and the current value is a non-animation
|
||||
|
// value, then the current entry becomes our base entry.
|
||||
|
if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited) |
||||
|
{ |
||||
|
Debug.Assert(_valueEntry is not null); |
||||
|
_baseValueEntry = _valueEntry; |
||||
|
_valueEntry = null; |
||||
|
} |
||||
|
|
||||
|
if (_valueEntry != entry) |
||||
|
{ |
||||
|
_valueEntry?.Unsubscribe(); |
||||
|
_valueEntry = entry; |
||||
|
} |
||||
|
} |
||||
|
else if (Priority <= BindingPriority.Animation) |
||||
|
{ |
||||
|
// We've received a non-animation value and have an active animation value, so the
|
||||
|
// new entry becomes our base entry.
|
||||
|
if (_baseValueEntry != entry) |
||||
|
{ |
||||
|
_baseValueEntry?.Unsubscribe(); |
||||
|
_baseValueEntry = entry; |
||||
|
} |
||||
|
} |
||||
|
else if (_valueEntry != entry) |
||||
|
{ |
||||
|
// Both the current value and the new value are non-animation values, so the new
|
||||
|
// entry replaces the existing entry.
|
||||
|
_valueEntry?.Unsubscribe(); |
||||
|
_valueEntry = entry; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void UnsubscribeValueEntries() |
||||
|
{ |
||||
|
_valueEntry?.Unsubscribe(); |
||||
|
_baseValueEntry?.Unsubscribe(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,270 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents the active value for a property in a <see cref="ValueStore"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Stores the active value in an <see cref="AvaloniaObject"/>'s <see cref="ValueStore"/>
|
||||
|
/// for a single property, when the value is not inherited or unset/default.
|
||||
|
/// </remarks>
|
||||
|
internal sealed class EffectiveValue<T> : EffectiveValue |
||||
|
{ |
||||
|
private readonly StyledPropertyMetadata<T> _metadata; |
||||
|
private T? _baseValue; |
||||
|
private UncommonFields? _uncommon; |
||||
|
|
||||
|
public EffectiveValue(AvaloniaObject owner, StyledPropertyBase<T> property) |
||||
|
{ |
||||
|
Priority = BindingPriority.Unset; |
||||
|
BasePriority = BindingPriority.Unset; |
||||
|
_metadata = property.GetMetadata(owner.GetType()); |
||||
|
|
||||
|
var value = _metadata.DefaultValue; |
||||
|
|
||||
|
if (property.HasCoercion && _metadata.CoerceValue is { } coerce) |
||||
|
{ |
||||
|
_uncommon = new() |
||||
|
{ |
||||
|
_coerce = coerce, |
||||
|
_uncoercedValue = value, |
||||
|
_uncoercedBaseValue = value, |
||||
|
}; |
||||
|
|
||||
|
Value = coerce(owner, value); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current effective value.
|
||||
|
/// </summary>
|
||||
|
public new T Value { get; private set; } |
||||
|
|
||||
|
public override void SetAndRaise( |
||||
|
ValueStore owner, |
||||
|
IValueEntry value, |
||||
|
BindingPriority priority) |
||||
|
{ |
||||
|
Debug.Assert(priority != BindingPriority.LocalValue); |
||||
|
UpdateValueEntry(value, priority); |
||||
|
|
||||
|
SetAndRaiseCore(owner, (StyledPropertyBase<T>)value.Property, GetValue(value), priority); |
||||
|
} |
||||
|
|
||||
|
public void SetLocalValueAndRaise( |
||||
|
ValueStore owner, |
||||
|
StyledPropertyBase<T> property, |
||||
|
T value) |
||||
|
{ |
||||
|
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue); |
||||
|
} |
||||
|
|
||||
|
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value) |
||||
|
{ |
||||
|
value = _baseValue!; |
||||
|
return BasePriority != BindingPriority.Unset; |
||||
|
} |
||||
|
|
||||
|
public override void RaiseInheritedValueChanged( |
||||
|
AvaloniaObject owner, |
||||
|
AvaloniaProperty property, |
||||
|
EffectiveValue? oldValue, |
||||
|
EffectiveValue? newValue) |
||||
|
{ |
||||
|
Debug.Assert(oldValue is not null || newValue is not null); |
||||
|
|
||||
|
var p = (StyledPropertyBase<T>)property; |
||||
|
var o = oldValue is not null ? ((EffectiveValue<T>)oldValue).Value : _metadata.DefaultValue; |
||||
|
var n = newValue is not null ? ((EffectiveValue<T>)newValue).Value : _metadata.DefaultValue; |
||||
|
var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; |
||||
|
|
||||
|
if (!EqualityComparer<T>.Default.Equals(o, n)) |
||||
|
{ |
||||
|
owner.RaisePropertyChanged(p, o, n, priority, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property) |
||||
|
{ |
||||
|
Debug.Assert(Priority != BindingPriority.Animation); |
||||
|
Debug.Assert(BasePriority != BindingPriority.Unset); |
||||
|
UpdateValueEntry(null, BindingPriority.Animation); |
||||
|
SetAndRaiseCore(owner, (StyledPropertyBase<T>)property, _baseValue!, BasePriority); |
||||
|
} |
||||
|
|
||||
|
public override void CoerceValue(ValueStore owner, AvaloniaProperty property) |
||||
|
{ |
||||
|
if (_uncommon is null) |
||||
|
return; |
||||
|
SetAndRaiseCore( |
||||
|
owner, |
||||
|
(StyledPropertyBase<T>)property, |
||||
|
_uncommon._uncoercedValue!, |
||||
|
Priority, |
||||
|
_uncommon._uncoercedBaseValue!, |
||||
|
BasePriority); |
||||
|
} |
||||
|
|
||||
|
public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) |
||||
|
{ |
||||
|
UnsubscribeValueEntries(); |
||||
|
DisposeAndRaiseUnset(owner, (StyledPropertyBase<T>)property); |
||||
|
} |
||||
|
|
||||
|
public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase<T> property) |
||||
|
{ |
||||
|
BindingPriority priority; |
||||
|
T oldValue; |
||||
|
|
||||
|
if (property.Inherits && owner.TryGetInheritedValue(property, out var i)) |
||||
|
{ |
||||
|
oldValue = ((EffectiveValue<T>)i).Value; |
||||
|
priority = BindingPriority.Inherited; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
oldValue = _metadata.DefaultValue; |
||||
|
priority = BindingPriority.Unset; |
||||
|
} |
||||
|
|
||||
|
if (!EqualityComparer<T>.Default.Equals(oldValue, Value)) |
||||
|
{ |
||||
|
owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true); |
||||
|
if (property.Inherits) |
||||
|
owner.OnInheritedEffectiveValueDisposed(property, Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override object? GetBoxedValue() => Value; |
||||
|
|
||||
|
private static T GetValue(IValueEntry entry) |
||||
|
{ |
||||
|
if (entry is IValueEntry<T> typed) |
||||
|
return typed.GetValue(); |
||||
|
else |
||||
|
return (T)entry.GetValue()!; |
||||
|
} |
||||
|
|
||||
|
private void SetAndRaiseCore( |
||||
|
ValueStore owner, |
||||
|
StyledPropertyBase<T> property, |
||||
|
T value, |
||||
|
BindingPriority priority) |
||||
|
{ |
||||
|
Debug.Assert(priority < BindingPriority.Inherited); |
||||
|
|
||||
|
var oldValue = Value; |
||||
|
var valueChanged = false; |
||||
|
var baseValueChanged = false; |
||||
|
var v = value; |
||||
|
|
||||
|
if (_uncommon?._coerce is { } coerce) |
||||
|
v = coerce(owner.Owner, value); |
||||
|
|
||||
|
if (priority <= Priority) |
||||
|
{ |
||||
|
valueChanged = !EqualityComparer<T>.Default.Equals(Value, v); |
||||
|
Value = v; |
||||
|
Priority = priority; |
||||
|
if (_uncommon is not null) |
||||
|
_uncommon._uncoercedValue = value; |
||||
|
} |
||||
|
|
||||
|
if (priority <= BasePriority && priority >= BindingPriority.LocalValue) |
||||
|
{ |
||||
|
baseValueChanged = !EqualityComparer<T>.Default.Equals(_baseValue, v); |
||||
|
_baseValue = v; |
||||
|
BasePriority = priority; |
||||
|
if (_uncommon is not null) |
||||
|
_uncommon._uncoercedBaseValue = value; |
||||
|
} |
||||
|
|
||||
|
if (valueChanged) |
||||
|
{ |
||||
|
using var notifying = PropertyNotifying.Start(owner.Owner, property); |
||||
|
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true); |
||||
|
if (property.Inherits) |
||||
|
owner.OnInheritedEffectiveValueChanged(property, oldValue, this); |
||||
|
} |
||||
|
else if (baseValueChanged) |
||||
|
{ |
||||
|
owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SetAndRaiseCore( |
||||
|
ValueStore owner, |
||||
|
StyledPropertyBase<T> property, |
||||
|
T value, |
||||
|
BindingPriority priority, |
||||
|
T baseValue, |
||||
|
BindingPriority basePriority) |
||||
|
{ |
||||
|
Debug.Assert(priority < BindingPriority.Inherited); |
||||
|
Debug.Assert(basePriority > BindingPriority.Animation); |
||||
|
Debug.Assert(priority <= basePriority); |
||||
|
|
||||
|
var oldValue = Value; |
||||
|
var valueChanged = false; |
||||
|
var baseValueChanged = false; |
||||
|
var v = value; |
||||
|
var bv = baseValue; |
||||
|
|
||||
|
if (_uncommon?._coerce is { } coerce) |
||||
|
{ |
||||
|
v = coerce(owner.Owner, value); |
||||
|
bv = coerce(owner.Owner, baseValue); |
||||
|
} |
||||
|
|
||||
|
if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v)) |
||||
|
{ |
||||
|
Value = v; |
||||
|
valueChanged = true; |
||||
|
if (_uncommon is not null) |
||||
|
_uncommon._uncoercedValue = value; |
||||
|
} |
||||
|
|
||||
|
if (priority != BindingPriority.Unset && |
||||
|
(BasePriority == BindingPriority.Unset || |
||||
|
!EqualityComparer<T>.Default.Equals(_baseValue, bv))) |
||||
|
{ |
||||
|
_baseValue = v; |
||||
|
baseValueChanged = true; |
||||
|
if (_uncommon is not null) |
||||
|
_uncommon._uncoercedValue = baseValue; |
||||
|
} |
||||
|
|
||||
|
Priority = priority; |
||||
|
BasePriority = basePriority; |
||||
|
|
||||
|
if (valueChanged) |
||||
|
{ |
||||
|
using var notifying = PropertyNotifying.Start(owner.Owner, property); |
||||
|
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true); |
||||
|
if (property.Inherits) |
||||
|
owner.OnInheritedEffectiveValueChanged(property, oldValue, this); |
||||
|
} |
||||
|
|
||||
|
if (baseValueChanged) |
||||
|
{ |
||||
|
owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class UncommonFields |
||||
|
{ |
||||
|
public Func<IAvaloniaObject, T, T>? _coerce; |
||||
|
public T? _uncoercedValue; |
||||
|
public T? _uncoercedBaseValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal enum FramePriority : sbyte |
||||
|
{ |
||||
|
Animation, |
||||
|
AnimationTemplatedParentTheme, |
||||
|
AnimationTheme, |
||||
|
StyleTrigger, |
||||
|
StyleTriggerTemplatedParentTheme, |
||||
|
StyleTriggerTheme, |
||||
|
Template, |
||||
|
TemplateTemplatedParentTheme, |
||||
|
TemplateTheme, |
||||
|
Style, |
||||
|
StyleTemplatedParentTheme, |
||||
|
StyleTheme, |
||||
|
} |
||||
|
|
||||
|
internal static class FramePriorityExtensions |
||||
|
{ |
||||
|
public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style) |
||||
|
{ |
||||
|
Debug.Assert(priority != BindingPriority.LocalValue); |
||||
|
var p = (int)(priority > 0 ? priority : priority + 1); |
||||
|
return (FramePriority)(p * 3 + (int)type); |
||||
|
} |
||||
|
|
||||
|
public static bool IsType(this FramePriority priority, FrameType type) |
||||
|
{ |
||||
|
return (FrameType)((int)priority % 3) == type; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,8 +0,0 @@ |
|||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
internal interface IBatchUpdate |
|
||||
{ |
|
||||
void BeginBatchUpdate(); |
|
||||
void EndBatchUpdate(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents an untyped interface to <see cref="IPriorityValueEntry{T}"/>.
|
|
||||
/// </summary>
|
|
||||
internal interface IPriorityValueEntry : IValue |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <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> |
|
||||
{ |
|
||||
void Reparent(PriorityValue<T> parent); |
|
||||
} |
|
||||
} |
|
||||
@ -1,28 +0,0 @@ |
|||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents an untyped interface to <see cref="IValue{T}"/>.
|
|
||||
/// </summary>
|
|
||||
internal interface IValue |
|
||||
{ |
|
||||
BindingPriority Priority { get; } |
|
||||
Optional<object?> GetValue(); |
|
||||
void Start(); |
|
||||
void RaiseValueChanged( |
|
||||
AvaloniaObject owner, |
|
||||
AvaloniaProperty property, |
|
||||
Optional<object?> oldValue, |
|
||||
Optional<object?> newValue); |
|
||||
} |
|
||||
|
|
||||
/// <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 |
|
||||
{ |
|
||||
Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,30 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an untyped value entry in a <see cref="ValueFrame"/>.
|
||||
|
/// </summary>
|
||||
|
internal interface IValueEntry |
||||
|
{ |
||||
|
bool HasValue { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the property that this value applies to.
|
||||
|
/// </summary>
|
||||
|
AvaloniaProperty Property { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value associated with the entry.
|
||||
|
/// </summary>
|
||||
|
/// <exception cref="AvaloniaInternalException">
|
||||
|
/// The entry has no value.
|
||||
|
/// </exception>
|
||||
|
object? GetValue(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when the value entry is removed from the value store.
|
||||
|
/// </summary>
|
||||
|
void Unsubscribe(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a typed value entry in a <see cref="ValueFrame"/>.
|
||||
|
/// </summary>
|
||||
|
internal interface IValueEntry<T> : IValueEntry |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the value associated with the entry.
|
||||
|
/// </summary>
|
||||
|
/// <exception cref="AvaloniaInternalException">
|
||||
|
/// The entry has no value.
|
||||
|
/// </exception>
|
||||
|
new T GetValue(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal class ImmediateValueEntry<T> : IValueEntry<T>, IDisposable |
||||
|
{ |
||||
|
private readonly ImmediateValueFrame _owner; |
||||
|
private readonly T _value; |
||||
|
|
||||
|
public ImmediateValueEntry( |
||||
|
ImmediateValueFrame owner, |
||||
|
StyledPropertyBase<T> property, |
||||
|
T value) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
_value = value; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
public StyledPropertyBase<T> Property { get; } |
||||
|
public bool HasValue => true; |
||||
|
AvaloniaProperty IValueEntry.Property => Property; |
||||
|
|
||||
|
public void Unsubscribe() { } |
||||
|
|
||||
|
public void Dispose() => _owner.OnEntryDisposed(this); |
||||
|
|
||||
|
object? IValueEntry.GetValue() => _value; |
||||
|
T IValueEntry<T>.GetValue() => _value; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Holds values in a <see cref="ValueStore"/> set by one of the SetValue or AddBinding
|
||||
|
/// overloads with non-LocalValue priority.
|
||||
|
/// </summary>
|
||||
|
internal sealed class ImmediateValueFrame : ValueFrame |
||||
|
{ |
||||
|
public ImmediateValueFrame(BindingPriority priority) |
||||
|
: base(priority, FrameType.Style) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TypedBindingEntry<T> AddBinding<T>( |
||||
|
StyledPropertyBase<T> property, |
||||
|
IObservable<BindingValue<T>> source) |
||||
|
{ |
||||
|
var e = new TypedBindingEntry<T>(this, property, source); |
||||
|
Add(e); |
||||
|
return e; |
||||
|
} |
||||
|
|
||||
|
public TypedBindingEntry<T> AddBinding<T>( |
||||
|
StyledPropertyBase<T> property, |
||||
|
IObservable<T> source) |
||||
|
{ |
||||
|
var e = new TypedBindingEntry<T>(this, property, source); |
||||
|
Add(e); |
||||
|
return e; |
||||
|
} |
||||
|
|
||||
|
public SourceUntypedBindingEntry<T> AddBinding<T>( |
||||
|
StyledPropertyBase<T> property, |
||||
|
IObservable<object?> source) |
||||
|
{ |
||||
|
var e = new SourceUntypedBindingEntry<T>(this, property, source); |
||||
|
Add(e); |
||||
|
return e; |
||||
|
} |
||||
|
|
||||
|
public ImmediateValueEntry<T> AddValue<T>(StyledPropertyBase<T> property, T value) |
||||
|
{ |
||||
|
var e = new ImmediateValueEntry<T>(this, property, value); |
||||
|
Add(e); |
||||
|
return e; |
||||
|
} |
||||
|
|
||||
|
public void OnEntryDisposed(IValueEntry value) |
||||
|
{ |
||||
|
Remove(value.Property); |
||||
|
Owner?.OnValueEntryRemoved(this, value.Property); |
||||
|
} |
||||
|
|
||||
|
protected override bool GetIsActive(out bool hasChanged) |
||||
|
{ |
||||
|
hasChanged = false; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal class LocalValueBindingObserver<T> : IObserver<T>, |
||||
|
IObserver<BindingValue<T>>, |
||||
|
IDisposable |
||||
|
{ |
||||
|
private readonly ValueStore _owner; |
||||
|
private IDisposable? _subscription; |
||||
|
|
||||
|
public LocalValueBindingObserver(ValueStore owner, StyledPropertyBase<T> property) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
public StyledPropertyBase<T> Property { get;} |
||||
|
|
||||
|
public void Start(IObservable<T> source) |
||||
|
{ |
||||
|
_subscription = source.Subscribe(this); |
||||
|
} |
||||
|
|
||||
|
public void Start(IObservable<BindingValue<T>> source) |
||||
|
{ |
||||
|
_subscription = source.Subscribe(this); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_subscription?.Dispose(); |
||||
|
_subscription = null; |
||||
|
_owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
} |
||||
|
|
||||
|
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
public void OnError(Exception error) => OnCompleted(); |
||||
|
|
||||
|
public void OnNext(T value) |
||||
|
{ |
||||
|
if (Property.ValidateValue?.Invoke(value) != false) |
||||
|
_owner.SetValue(Property, value, BindingPriority.LocalValue); |
||||
|
else |
||||
|
_owner.ClearLocalValue(Property); |
||||
|
} |
||||
|
|
||||
|
public void OnNext(BindingValue<T> value) |
||||
|
{ |
||||
|
LoggingUtils.LogIfNecessary(_owner.Owner, Property, value); |
||||
|
|
||||
|
if (value.HasValue) |
||||
|
_owner.SetValue(Property, value.Value, BindingPriority.LocalValue); |
||||
|
else if (value.Type != BindingValueType.DataValidationError) |
||||
|
_owner.ClearLocalValue(Property); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,41 +0,0 @@ |
|||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
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 BindingPriority Priority => BindingPriority.LocalValue; |
|
||||
Optional<object?> IValue.GetValue() => new Optional<object?>(_value); |
|
||||
|
|
||||
public Optional<T> GetValue(BindingPriority maxPriority) |
|
||||
{ |
|
||||
return BindingPriority.LocalValue >= maxPriority ? _value : Optional<T>.Empty; |
|
||||
} |
|
||||
|
|
||||
public void SetValue(T value) => _value = value; |
|
||||
public void Start() { } |
|
||||
|
|
||||
public void RaiseValueChanged( |
|
||||
AvaloniaObject owner, |
|
||||
AvaloniaProperty property, |
|
||||
Optional<object?> oldValue, |
|
||||
Optional<object?> newValue) |
|
||||
{ |
|
||||
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
owner, |
|
||||
(AvaloniaProperty<T>)property, |
|
||||
oldValue.Cast<T>(), |
|
||||
newValue.Cast<T>(), |
|
||||
BindingPriority.LocalValue)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,62 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal class LocalValueUntypedBindingObserver<T> : IObserver<object?>, |
||||
|
IDisposable |
||||
|
{ |
||||
|
private readonly ValueStore _owner; |
||||
|
private IDisposable? _subscription; |
||||
|
|
||||
|
public LocalValueUntypedBindingObserver(ValueStore owner, StyledPropertyBase<T> property) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
public StyledPropertyBase<T> Property { get; } |
||||
|
|
||||
|
public void Start(IObservable<object?> source) |
||||
|
{ |
||||
|
_subscription = source.Subscribe(this); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_subscription?.Dispose(); |
||||
|
_subscription = null; |
||||
|
_owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
} |
||||
|
|
||||
|
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); |
||||
|
public void OnError(Exception error) => OnCompleted(); |
||||
|
|
||||
|
public void OnNext(object? value) |
||||
|
{ |
||||
|
if (value is BindingNotification n) |
||||
|
{ |
||||
|
value = n.Value; |
||||
|
LoggingUtils.LogIfNecessary(_owner.Owner, Property, n); |
||||
|
} |
||||
|
|
||||
|
if (value == AvaloniaProperty.UnsetValue) |
||||
|
{ |
||||
|
_owner.ClearLocalValue(Property); |
||||
|
} |
||||
|
else if (value == BindingOperations.DoNothing) |
||||
|
{ |
||||
|
// Do nothing!
|
||||
|
} |
||||
|
else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue)) |
||||
|
{ |
||||
|
_owner.SetValue(Property, typedValue, BindingPriority.LocalValue); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_owner.ClearLocalValue(Property); |
||||
|
LoggingUtils.LogInvalidValue(_owner.Owner, Property, typeof(T), value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal static class LoggingUtils |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void LogIfNecessary( |
||||
|
AvaloniaObject owner, |
||||
|
AvaloniaProperty property, |
||||
|
BindingNotification value) |
||||
|
{ |
||||
|
if (value.ErrorType != BindingErrorType.None) |
||||
|
Log(owner, property, value.Error!); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void LogIfNecessary<T>( |
||||
|
AvaloniaObject owner, |
||||
|
AvaloniaProperty property, |
||||
|
BindingValue<T> value) |
||||
|
{ |
||||
|
if (value.HasError) |
||||
|
Log(owner, property, value.Error!); |
||||
|
} |
||||
|
|
||||
|
public static void LogInvalidValue( |
||||
|
AvaloniaObject owner, |
||||
|
AvaloniaProperty property, |
||||
|
Type expectedType, |
||||
|
object? value) |
||||
|
{ |
||||
|
if (value is not null) |
||||
|
{ |
||||
|
owner.GetBindingWarningLogger(property, null)?.Log( |
||||
|
owner, |
||||
|
"Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})", |
||||
|
owner, |
||||
|
property, |
||||
|
expectedType, |
||||
|
value, |
||||
|
value.GetType()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
owner.GetBindingWarningLogger(property, null)?.Log( |
||||
|
owner, |
||||
|
"Error in binding to {Target}.{Property}: expected {ExpectedType}, got null", |
||||
|
owner, |
||||
|
property, |
||||
|
expectedType); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void Log( |
||||
|
AvaloniaObject owner, |
||||
|
AvaloniaProperty property, |
||||
|
Exception e) |
||||
|
{ |
||||
|
if (e is TargetInvocationException t) |
||||
|
e = t.InnerException!; |
||||
|
|
||||
|
if (e is AggregateException a) |
||||
|
{ |
||||
|
foreach (var i in a.InnerExceptions) |
||||
|
Log(owner, property, i); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
owner.GetBindingWarningLogger(property, e)?.Log( |
||||
|
owner, |
||||
|
"Error in binding to {Target}.{Property}: {Message}", |
||||
|
owner, |
||||
|
property, |
||||
|
e.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,326 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents an untyped interface to <see cref="PriorityValue{T}"/>.
|
|
||||
/// </summary>
|
|
||||
interface IPriorityValue : IValue |
|
||||
{ |
|
||||
void UpdateEffectiveValue(); |
|
||||
} |
|
||||
|
|
||||
/// <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> : IPriorityValue, IValue<T>, IBatchUpdate |
|
||||
{ |
|
||||
private readonly AvaloniaObject _owner; |
|
||||
private readonly ValueStore _store; |
|
||||
private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>(); |
|
||||
private readonly Func<IAvaloniaObject, T, T>? _coerceValue; |
|
||||
private Optional<T> _localValue; |
|
||||
private Optional<T> _value; |
|
||||
private bool _isCalculatingValue; |
|
||||
private bool _batchUpdate; |
|
||||
|
|
||||
public PriorityValue( |
|
||||
AvaloniaObject owner, |
|
||||
StyledPropertyBase<T> property, |
|
||||
ValueStore store) |
|
||||
{ |
|
||||
_owner = owner; |
|
||||
Property = property; |
|
||||
_store = store; |
|
||||
|
|
||||
if (property.HasCoercion) |
|
||||
{ |
|
||||
var metadata = property.GetMetadata(owner.GetType()); |
|
||||
_coerceValue = metadata.CoerceValue; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public PriorityValue( |
|
||||
AvaloniaObject owner, |
|
||||
StyledPropertyBase<T> property, |
|
||||
ValueStore store, |
|
||||
IPriorityValueEntry<T> existing) |
|
||||
: this(owner, property, store) |
|
||||
{ |
|
||||
existing.Reparent(this); |
|
||||
_entries.Add(existing); |
|
||||
|
|
||||
if (existing is IBindingEntry binding && |
|
||||
existing.Priority == BindingPriority.LocalValue) |
|
||||
{ |
|
||||
// Bit of a special case here: if we have a local value binding that is being
|
|
||||
// promoted to a priority value we need to make sure the binding is subscribed
|
|
||||
// even if we've got a batch operation in progress because otherwise we don't know
|
|
||||
// whether the binding or a subsequent SetValue with local priority will win. A
|
|
||||
// notification won't be sent during batch update anyway because it will be
|
|
||||
// caught and stored for later by the ValueStore.
|
|
||||
binding.Start(ignoreBatchUpdate: true); |
|
||||
} |
|
||||
|
|
||||
var v = existing.GetValue(); |
|
||||
|
|
||||
if (v.HasValue) |
|
||||
{ |
|
||||
_value = v; |
|
||||
Priority = existing.Priority; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public PriorityValue( |
|
||||
AvaloniaObject owner, |
|
||||
StyledPropertyBase<T> property, |
|
||||
ValueStore sink, |
|
||||
LocalValueEntry<T> existing) |
|
||||
: this(owner, property, sink) |
|
||||
{ |
|
||||
_value = _localValue = existing.GetValue(BindingPriority.LocalValue); |
|
||||
Priority = BindingPriority.LocalValue; |
|
||||
} |
|
||||
|
|
||||
public StyledPropertyBase<T> Property { get; } |
|
||||
public BindingPriority Priority { get; private set; } = BindingPriority.Unset; |
|
||||
public IReadOnlyList<IPriorityValueEntry<T>> Entries => _entries; |
|
||||
Optional<object?> IValue.GetValue() => _value.ToObject(); |
|
||||
|
|
||||
public void BeginBatchUpdate() |
|
||||
{ |
|
||||
_batchUpdate = true; |
|
||||
|
|
||||
foreach (var entry in _entries) |
|
||||
{ |
|
||||
(entry as IBatchUpdate)?.BeginBatchUpdate(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void EndBatchUpdate() |
|
||||
{ |
|
||||
_batchUpdate = false; |
|
||||
|
|
||||
foreach (var entry in _entries) |
|
||||
{ |
|
||||
(entry as IBatchUpdate)?.EndBatchUpdate(); |
|
||||
} |
|
||||
|
|
||||
UpdateEffectiveValue(null); |
|
||||
} |
|
||||
|
|
||||
public void ClearLocalValue() |
|
||||
{ |
|
||||
_localValue = default; |
|
||||
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
_owner, |
|
||||
Property, |
|
||||
default, |
|
||||
default, |
|
||||
BindingPriority.LocalValue)); |
|
||||
} |
|
||||
|
|
||||
public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation) |
|
||||
{ |
|
||||
if (Priority == BindingPriority.Unset) |
|
||||
{ |
|
||||
return default; |
|
||||
} |
|
||||
|
|
||||
if (Priority >= maxPriority) |
|
||||
{ |
|
||||
return _value; |
|
||||
} |
|
||||
|
|
||||
return CalculateValue(maxPriority).Item1; |
|
||||
} |
|
||||
|
|
||||
public IDisposable? SetValue(T value, BindingPriority priority) |
|
||||
{ |
|
||||
IDisposable? result = null; |
|
||||
|
|
||||
if (priority == BindingPriority.LocalValue) |
|
||||
{ |
|
||||
_localValue = value; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
var insert = FindInsertPoint(priority); |
|
||||
var entry = new ConstantValueEntry<T>(Property, value, priority, new ValueOwner<T>(this)); |
|
||||
_entries.Insert(insert, entry); |
|
||||
result = entry; |
|
||||
} |
|
||||
|
|
||||
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
_owner, |
|
||||
Property, |
|
||||
default, |
|
||||
value, |
|
||||
priority)); |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority) |
|
||||
{ |
|
||||
var binding = new BindingEntry<T>(_owner, Property, source, priority, new(this)); |
|
||||
var insert = FindInsertPoint(binding.Priority); |
|
||||
_entries.Insert(insert, binding); |
|
||||
|
|
||||
if (_batchUpdate) |
|
||||
{ |
|
||||
binding.BeginBatchUpdate(); |
|
||||
|
|
||||
if (priority == BindingPriority.LocalValue) |
|
||||
{ |
|
||||
binding.Start(ignoreBatchUpdate: true); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return binding; |
|
||||
} |
|
||||
|
|
||||
public void UpdateEffectiveValue() => UpdateEffectiveValue(null); |
|
||||
public void Start() => UpdateEffectiveValue(null); |
|
||||
|
|
||||
public void RaiseValueChanged( |
|
||||
AvaloniaObject owner, |
|
||||
AvaloniaProperty property, |
|
||||
Optional<object?> oldValue, |
|
||||
Optional<object?> newValue) |
|
||||
{ |
|
||||
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
owner, |
|
||||
(AvaloniaProperty<T>)property, |
|
||||
oldValue.Cast<T>(), |
|
||||
newValue.Cast<T>(), |
|
||||
Priority)); |
|
||||
} |
|
||||
|
|
||||
public void ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change) |
|
||||
{ |
|
||||
if (change.Priority == BindingPriority.LocalValue) |
|
||||
{ |
|
||||
_localValue = default; |
|
||||
} |
|
||||
|
|
||||
if (!_isCalculatingValue && change is AvaloniaPropertyChangedEventArgs<T> c) |
|
||||
{ |
|
||||
UpdateEffectiveValue(c); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Completed(IPriorityValueEntry entry, Optional<T> oldValue) |
|
||||
{ |
|
||||
_entries.Remove((IPriorityValueEntry<T>)entry); |
|
||||
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
_owner, |
|
||||
Property, |
|
||||
oldValue, |
|
||||
default, |
|
||||
entry.Priority)); |
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
} |
|
||||
|
|
||||
public (Optional<T>, BindingPriority) CalculateValue(BindingPriority maxPriority) |
|
||||
{ |
|
||||
_isCalculatingValue = true; |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
for (var i = _entries.Count - 1; i >= 0; --i) |
|
||||
{ |
|
||||
var entry = _entries[i]; |
|
||||
|
|
||||
if (entry.Priority < maxPriority) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
entry.Start(); |
|
||||
|
|
||||
if (entry.Priority >= BindingPriority.LocalValue && |
|
||||
maxPriority <= BindingPriority.LocalValue && |
|
||||
_localValue.HasValue) |
|
||||
{ |
|
||||
return (_localValue, BindingPriority.LocalValue); |
|
||||
} |
|
||||
|
|
||||
var entryValue = entry.GetValue(); |
|
||||
|
|
||||
if (entryValue.HasValue) |
|
||||
{ |
|
||||
return (entryValue, entry.Priority); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (maxPriority <= BindingPriority.LocalValue && _localValue.HasValue) |
|
||||
{ |
|
||||
return (_localValue, BindingPriority.LocalValue); |
|
||||
} |
|
||||
|
|
||||
return (default, BindingPriority.Unset); |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
_isCalculatingValue = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void UpdateEffectiveValue(AvaloniaPropertyChangedEventArgs<T>? change) |
|
||||
{ |
|
||||
var (value, priority) = CalculateValue(BindingPriority.Animation); |
|
||||
|
|
||||
if (value.HasValue && _coerceValue != null) |
|
||||
{ |
|
||||
value = _coerceValue(_owner, value.Value); |
|
||||
} |
|
||||
|
|
||||
Priority = priority; |
|
||||
|
|
||||
if (value != _value) |
|
||||
{ |
|
||||
var old = _value; |
|
||||
_value = value; |
|
||||
|
|
||||
_store.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( |
|
||||
_owner, |
|
||||
Property, |
|
||||
old, |
|
||||
value, |
|
||||
Priority)); |
|
||||
} |
|
||||
else if (change is object) |
|
||||
{ |
|
||||
change.MarkNonEffectiveValue(); |
|
||||
change.SetOldValue(default); |
|
||||
_store.ValueChanged(change); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Raises <see cref="AvaloniaProperty.Notifying"/> where necessary.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Uses the disposable pattern to ensure that the closing Notifying call is made even in the
|
||||
|
/// presence of exceptions.
|
||||
|
/// </remarks>
|
||||
|
internal readonly struct PropertyNotifying : IDisposable |
||||
|
{ |
||||
|
private readonly AvaloniaObject _owner; |
||||
|
private readonly AvaloniaProperty _property; |
||||
|
|
||||
|
private PropertyNotifying(AvaloniaObject owner, AvaloniaProperty property) |
||||
|
{ |
||||
|
Debug.Assert(property.Notifying is not null); |
||||
|
_owner = owner; |
||||
|
_property = property; |
||||
|
_property.Notifying!(owner, true); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() => _property.Notifying!(_owner, false); |
||||
|
|
||||
|
public static PropertyNotifying? Start(AvaloniaObject owner, AvaloniaProperty property) |
||||
|
{ |
||||
|
if (property.Notifying is null) |
||||
|
return null; |
||||
|
return new PropertyNotifying(owner, property); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An <see cref="IValueEntry"/> that holds a binding whose source observable is untyped and
|
||||
|
/// target property is typed.
|
||||
|
/// </summary>
|
||||
|
internal sealed class SourceUntypedBindingEntry<TTarget> : BindingEntryBase<TTarget, object?> |
||||
|
{ |
||||
|
private readonly Func<TTarget, bool>? _validate; |
||||
|
|
||||
|
public SourceUntypedBindingEntry( |
||||
|
ValueFrame frame, |
||||
|
StyledPropertyBase<TTarget> property, |
||||
|
IObservable<object?> source) |
||||
|
: base(frame, property, source) |
||||
|
{ |
||||
|
_validate = property.ValidateValue; |
||||
|
} |
||||
|
|
||||
|
public new StyledPropertyBase<TTarget> Property => (StyledPropertyBase<TTarget>)base.Property; |
||||
|
|
||||
|
protected override BindingValue<TTarget> ConvertAndValidate(object? value) |
||||
|
{ |
||||
|
return UntypedValueUtils.ConvertAndValidate(value, Property.PropertyType, _validate); |
||||
|
} |
||||
|
|
||||
|
protected override BindingValue<TTarget> ConvertAndValidate(BindingValue<object?> value) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An <see cref="IValueEntry"/> that holds a binding whose source observable and target
|
||||
|
/// property are both typed.
|
||||
|
/// </summary>
|
||||
|
internal sealed class TypedBindingEntry<T> : BindingEntryBase<T, T> |
||||
|
{ |
||||
|
public TypedBindingEntry( |
||||
|
ValueFrame frame, |
||||
|
StyledPropertyBase<T> property, |
||||
|
IObservable<T> source) |
||||
|
: base(frame, property, source) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TypedBindingEntry( |
||||
|
ValueFrame frame, |
||||
|
StyledPropertyBase<T> property, |
||||
|
IObservable<BindingValue<T>> source) |
||||
|
: base(frame, property, source) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new StyledPropertyBase<T> Property => (StyledPropertyBase<T>)base.Property; |
||||
|
|
||||
|
protected override BindingValue<T> ConvertAndValidate(T value) |
||||
|
{ |
||||
|
if (Property.ValidateValue?.Invoke(value) == false) |
||||
|
{ |
||||
|
return BindingValue<T>.BindingError( |
||||
|
new InvalidCastException($"'{value}' is not a valid value.")); |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
protected override BindingValue<T> ConvertAndValidate(BindingValue<T> value) |
||||
|
{ |
||||
|
if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false) |
||||
|
{ |
||||
|
return BindingValue<T>.BindingError( |
||||
|
new InvalidCastException($"'{value.Value}' is not a valid value.")); |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An <see cref="IValueEntry"/> that holds a binding whose source observable and target
|
||||
|
/// property are both untyped.
|
||||
|
/// </summary>
|
||||
|
internal class UntypedBindingEntry : BindingEntryBase<object?, object?> |
||||
|
{ |
||||
|
private readonly Func<object?, bool>? _validate; |
||||
|
|
||||
|
public UntypedBindingEntry( |
||||
|
ValueFrame frame, |
||||
|
AvaloniaProperty property, |
||||
|
IObservable<object?> source) |
||||
|
: base(frame, property, source) |
||||
|
{ |
||||
|
_validate = ((IStyledPropertyAccessor)property).ValidateValue; |
||||
|
} |
||||
|
|
||||
|
protected override BindingValue<object?> ConvertAndValidate(object? value) |
||||
|
{ |
||||
|
return UntypedValueUtils.ConvertAndValidate(value, Property.PropertyType, _validate); |
||||
|
} |
||||
|
|
||||
|
protected override BindingValue<object?> ConvertAndValidate(BindingValue<object?> value) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal static class UntypedValueUtils |
||||
|
{ |
||||
|
public static BindingValue<T> ConvertAndValidate<T>( |
||||
|
object? value, |
||||
|
Type targetType, |
||||
|
Func<T, bool>? validate) |
||||
|
{ |
||||
|
var v = BindingValue<T>.FromUntyped(value, targetType); |
||||
|
|
||||
|
if (v.HasValue && validate?.Invoke(v.Value) == false) |
||||
|
{ |
||||
|
return BindingValue<T>.BindingError( |
||||
|
new InvalidCastException($"'{v.Value}' is not a valid value.")); |
||||
|
} |
||||
|
|
||||
|
return v; |
||||
|
} |
||||
|
|
||||
|
public static bool TryConvertAndValidate( |
||||
|
AvaloniaProperty property, |
||||
|
object? value, |
||||
|
out object? result) |
||||
|
{ |
||||
|
if (TypeUtilities.TryConvertImplicit(property.PropertyType, value, out result)) |
||||
|
return ((IStyledPropertyAccessor)property).ValidateValue(result); |
||||
|
|
||||
|
result = default; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static bool TryConvertAndValidate<T>( |
||||
|
StyledPropertyBase<T> property, |
||||
|
object? value, |
||||
|
[MaybeNullWhen(false)] out T result) |
||||
|
{ |
||||
|
if (TypeUtilities.TryConvertImplicit(typeof(T), value, out var v)) |
||||
|
{ |
||||
|
result = (T)v!; |
||||
|
|
||||
|
if (property.ValidateValue?.Invoke(result) != false) |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
result = default; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal enum FrameType |
||||
|
{ |
||||
|
Style, |
||||
|
TemplatedParentTheme, |
||||
|
Theme, |
||||
|
} |
||||
|
|
||||
|
internal abstract class ValueFrame |
||||
|
{ |
||||
|
private List<IValueEntry>? _entries; |
||||
|
private AvaloniaPropertyDictionary<IValueEntry> _index; |
||||
|
private ValueStore? _owner; |
||||
|
private bool _isShared; |
||||
|
|
||||
|
protected ValueFrame(BindingPriority priority, FrameType type) |
||||
|
{ |
||||
|
Priority = priority; |
||||
|
FramePriority = priority.ToFramePriority(type); |
||||
|
} |
||||
|
|
||||
|
public int EntryCount => _index.Count; |
||||
|
public bool IsActive => GetIsActive(out _); |
||||
|
public ValueStore? Owner => !_isShared ? _owner : |
||||
|
throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame"); |
||||
|
public BindingPriority Priority { get; } |
||||
|
public FramePriority FramePriority { get; } |
||||
|
|
||||
|
public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property); |
||||
|
|
||||
|
public IValueEntry GetEntry(int index) => _entries?[index] ?? _index[0]; |
||||
|
|
||||
|
public void SetOwner(ValueStore? owner) |
||||
|
{ |
||||
|
if (_owner is not null && owner is not null) |
||||
|
throw new AvaloniaInternalException("ValueFrame already has an owner."); |
||||
|
if (!_isShared) |
||||
|
_owner = owner; |
||||
|
} |
||||
|
|
||||
|
public bool TryGetEntryIfActive( |
||||
|
AvaloniaProperty property, |
||||
|
[NotNullWhen(true)] out IValueEntry? entry, |
||||
|
out bool activeChanged) |
||||
|
{ |
||||
|
if (_index.TryGetValue(property, out entry)) |
||||
|
return GetIsActive(out activeChanged); |
||||
|
activeChanged = false; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public void OnBindingCompleted(IValueEntry binding) |
||||
|
{ |
||||
|
var property = binding.Property; |
||||
|
Remove(property); |
||||
|
Owner?.OnValueEntryRemoved(this, property); |
||||
|
} |
||||
|
|
||||
|
public virtual void Dispose() |
||||
|
{ |
||||
|
for (var i = 0; i < _index.Count; ++i) |
||||
|
_index[i].Unsubscribe(); |
||||
|
} |
||||
|
|
||||
|
protected abstract bool GetIsActive(out bool hasChanged); |
||||
|
|
||||
|
protected void MakeShared() |
||||
|
{ |
||||
|
_isShared = true; |
||||
|
_owner = null; |
||||
|
} |
||||
|
|
||||
|
protected void Add(IValueEntry value) |
||||
|
{ |
||||
|
Debug.Assert(!value.Property.IsDirect); |
||||
|
|
||||
|
if (_entries is null && _index.Count == 1) |
||||
|
{ |
||||
|
_entries = new(); |
||||
|
_entries.Add(_index[0]); |
||||
|
} |
||||
|
|
||||
|
_index.Add(value.Property, value); |
||||
|
_entries?.Add(value); |
||||
|
} |
||||
|
|
||||
|
protected void Remove(AvaloniaProperty property) |
||||
|
{ |
||||
|
Debug.Assert(!property.IsDirect); |
||||
|
|
||||
|
if (_entries is not null) |
||||
|
{ |
||||
|
var count = _entries.Count; |
||||
|
|
||||
|
for (var i = 0; i < count; ++i) |
||||
|
{ |
||||
|
if (_entries[i].Property == property) |
||||
|
{ |
||||
|
_entries.RemoveAt(i); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_index.Remove(property); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,45 +0,0 @@ |
|||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents a union type of <see cref="ValueStore"/> and <see cref="PriorityValue{T}"/>,
|
|
||||
/// which are the valid owners of a value store <see cref="IValue"/>.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
|
||||
internal readonly struct ValueOwner<T> |
|
||||
{ |
|
||||
private readonly ValueStore? _store; |
|
||||
private readonly PriorityValue<T>? _priorityValue; |
|
||||
|
|
||||
public ValueOwner(ValueStore o) |
|
||||
{ |
|
||||
_store = o; |
|
||||
_priorityValue = null; |
|
||||
} |
|
||||
|
|
||||
public ValueOwner(PriorityValue<T> v) |
|
||||
{ |
|
||||
_store = null; |
|
||||
_priorityValue = v; |
|
||||
} |
|
||||
|
|
||||
public bool IsValueStore => _store is not null; |
|
||||
|
|
||||
public void Completed(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue) |
|
||||
{ |
|
||||
if (_store is not null) |
|
||||
_store?.Completed(property, entry, oldValue); |
|
||||
else |
|
||||
_priorityValue!.Completed(entry, oldValue); |
|
||||
} |
|
||||
|
|
||||
public void ValueChanged(AvaloniaPropertyChangedEventArgs<T> e) |
|
||||
{ |
|
||||
if (_store is not null) |
|
||||
_store?.ValueChanged(e); |
|
||||
else |
|
||||
_priorityValue!.ValueChanged(e); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
@ -1,59 +0,0 @@ |
|||||
using System; |
|
||||
using System.Reactive.Subjects; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
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)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,33 +0,0 @@ |
|||||
using System; |
|
||||
using System.Reactive.Subjects; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,62 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Data; |
|
||||
using Avalonia.Logging; |
|
||||
|
|
||||
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) |
|
||||
{ |
|
||||
var unwrappedValue = value.HasValue ? value.Value : null; |
|
||||
|
|
||||
Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log( |
|
||||
_target, |
|
||||
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", |
|
||||
_property.Name, |
|
||||
_property.PropertyType, |
|
||||
unwrappedValue, |
|
||||
unwrappedValue?.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(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,55 +0,0 @@ |
|||||
using System; |
|
||||
using System.Reactive.Subjects; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
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()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue