using System; using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.Data; namespace Avalonia.PropertyStore { internal abstract class BindingEntryBase : IValueEntry, IObserver, IObserver>, 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> source) { Frame = frame; Source = source; Property = property; } protected BindingEntryBase( ValueFrame frame, AvaloniaProperty property, IObservable 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 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 has no value."); return _value!; } protected abstract BindingValue ConvertAndValidate(TSource value); protected abstract BindingValue ConvertAndValidate(BindingValue value); protected virtual void Start(bool produceValue) { if (_subscription is not null) return; _subscription = produceValue ? s_creating : s_creatingQuiet; _subscription = Source switch { IObservable> bv => bv.Subscribe(this), IObservable 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 value) { if (Frame.Owner is null) return; LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value); if (value.HasValue) { if (!_hasValue || !EqualityComparer.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); } } }