Browse Source
We already have a place to store them in the `_localValueBindings` list, so use that rather than having a separate `_directBindings` list in `AvaloniaObject`.refactor/style-priorities
15 changed files with 285 additions and 417 deletions
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue