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