Browse Source

Make ActivatedValue a custom Observable.

And make `ActivatedObservable` inherit from it.
pull/1690/head
Steven Kirk 9 years ago
committed by Steven Kirk
parent
commit
cdba9b856d
  1. 125
      src/Avalonia.Styling/Styling/ActivatedObservable.cs
  2. 147
      src/Avalonia.Styling/Styling/ActivatedValue.cs

125
src/Avalonia.Styling/Styling/ActivatedObservable.cs

@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
@ -17,14 +15,9 @@ namespace Avalonia.Styling
/// value. When the activator produces false it will produce /// value. When the activator produces false it will produce
/// <see cref="AvaloniaProperty.UnsetValue"/>. /// <see cref="AvaloniaProperty.UnsetValue"/>.
/// </remarks> /// </remarks>
internal class ActivatedObservable : IObservable<object>, IDescription internal class ActivatedObservable : ActivatedValue, IDescription
{ {
private static readonly object NotSent = new object();
private readonly Listener _listener;
private List<IObserver<object>> _observers;
private IDisposable _activatorSubscription;
private IDisposable _sourceSubscription; private IDisposable _sourceSubscription;
private object _last = NotSent;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class. /// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
@ -36,133 +29,49 @@ namespace Avalonia.Styling
IObservable<bool> activator, IObservable<bool> activator,
IObservable<object> source, IObservable<object> source,
string description) string description)
: base(activator, AvaloniaProperty.UnsetValue, description)
{ {
Contract.Requires<ArgumentNullException>(activator != null);
Contract.Requires<ArgumentNullException>(source != null); Contract.Requires<ArgumentNullException>(source != null);
Activator = activator;
Description = description;
Source = source; Source = source;
_listener = new Listener(this);
} }
/// <summary>
/// Gets the activator observable.
/// </summary>
public IObservable<bool> Activator { get; }
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets a value indicating whether the observable is active.
/// </summary>
public bool? IsActive { get; private set; }
/// <summary> /// <summary>
/// Gets an observable which produces the <see cref="ActivatedValue"/>. /// Gets an observable which produces the <see cref="ActivatedValue"/>.
/// </summary> /// </summary>
public IObservable<object> Source { get; } public IObservable<object> Source { get; }
/// <summary> protected override ActivatorListener CreateListener() => new ValueListener(this);
/// Gets the value that will be produced when <see cref="IsActive"/> is true.
/// </summary>
public object Value { get; private set; }
public IDisposable Subscribe(IObserver<object> observer)
{
var subscribe = _observers == null;
_observers = _observers ?? new List<IObserver<object>>();
_observers.Add(observer);
if (subscribe)
{
_sourceSubscription = Source.Subscribe(_listener);
_activatorSubscription = Activator.Subscribe(_listener);
}
return Disposable.Create(() =>
{
_observers.Remove(observer);
if (_observers.Count == 0)
{
_activatorSubscription.Dispose();
_sourceSubscription.Dispose();
_activatorSubscription = null;
_sourceSubscription = null;
}
});
}
protected virtual void NotifyCompleted() protected override void Deinitialize()
{ {
foreach (var observer in _observers) base.Deinitialize();
{ _sourceSubscription.Dispose();
observer.OnCompleted(); _sourceSubscription = null;
}
_observers = null;
} }
protected virtual void NotifyError(Exception error) protected override void Initialize()
{ {
foreach (var observer in _observers) base.Initialize();
{ _sourceSubscription = Source.Subscribe((ValueListener)Listener);
observer.OnError(error);
}
_observers = null;
} }
protected virtual void NotifyValue(object value) protected virtual void NotifyValue(object value)
{ {
Value = value; Value = value;
Update();
} }
protected virtual void NotifyActive(bool active) private class ValueListener : ActivatorListener, IObserver<object>
{ {
IsActive = active; public ValueListener(ActivatedObservable parent)
Update(); : base(parent)
}
private void Update()
{
if (IsActive.HasValue)
{
var v = IsActive.Value ? Value : AvaloniaProperty.UnsetValue;
if (!Equals(v, _last))
{
foreach (var observer in _observers)
{
observer.OnNext(v);
}
_last = v;
}
}
}
private class Listener : IObserver<bool>, IObserver<object>
{
private readonly ActivatedObservable _parent;
public Listener(ActivatedObservable parent)
{ {
_parent = parent;
} }
protected new ActivatedObservable Parent => (ActivatedObservable)base.Parent;
void IObserver<bool>.OnCompleted() => _parent.NotifyCompleted(); void IObserver<object>.OnCompleted() => Parent.NotifyCompleted();
void IObserver<object>.OnCompleted() => _parent.NotifyCompleted(); void IObserver<object>.OnError(Exception error) => Parent.NotifyError(error);
void IObserver<bool>.OnError(Exception error) => _parent.NotifyError(error); void IObserver<object>.OnNext(object value) => Parent.NotifyValue(value);
void IObserver<object>.OnError(Exception error) => _parent.NotifyError(error);
void IObserver<bool>.OnNext(bool value) => _parent.NotifyActive(value);
void IObserver<object>.OnNext(object value) => _parent.NotifyValue(value);
} }
} }
} }

147
src/Avalonia.Styling/Styling/ActivatedValue.cs

@ -2,8 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive; using System.Collections.Generic;
using System.Reactive.Linq; using System.Reactive.Disposables;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
@ -16,12 +16,13 @@ namespace Avalonia.Styling
/// <see cref="ActivatedValue"/> will produce the current value. When the activator /// <see cref="ActivatedValue"/> will produce the current value. When the activator
/// produces false it will produce <see cref="AvaloniaProperty.UnsetValue"/>. /// produces false it will produce <see cref="AvaloniaProperty.UnsetValue"/>.
/// </remarks> /// </remarks>
internal class ActivatedValue : ObservableBase<object>, IDescription internal class ActivatedValue : IObservable<object>, IDescription
{ {
/// <summary> private static readonly object NotSent = new object();
/// The activator. private List<IObserver<object>> _observers = new List<IObserver<object>>();
/// </summary> private IDisposable _activatorSubscription;
private readonly IObservable<bool> _activator; private object _value;
private object _last = NotSent;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class. /// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
@ -34,39 +35,135 @@ namespace Avalonia.Styling
object value, object value,
string description) string description)
{ {
_activator = activator; Contract.Requires<ArgumentNullException>(activator != null);
Activator = activator;
Value = value; Value = value;
Description = description; Description = description;
Listener = CreateListener();
} }
/// <summary> /// <summary>
/// Gets the activated value. /// Gets the activator observable.
/// </summary> /// </summary>
public object Value public IObservable<bool> Activator { get; }
{
get;
}
/// <summary> /// <summary>
/// Gets a description of the binding. /// Gets a description of the binding.
/// </summary> /// </summary>
public string Description public string Description { get; }
{
get; /// <summary>
} /// Gets a value indicating whether the activator is active.
/// </summary>
public bool? IsActive { get; private set; }
/// <summary> /// <summary>
/// Notifies the provider that an observer is to receive notifications. /// Gets the value that will be produced when <see cref="IsActive"/> is true.
/// </summary> /// </summary>
/// <param name="observer">The observer.</param> public object Value
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns> {
protected override IDisposable SubscribeCore(IObserver<object> observer) get => _value;
protected set
{
_value = value;
PublishValue();
}
}
protected ActivatorListener Listener { get; }
public virtual IDisposable Subscribe(IObserver<object> observer)
{
_observers.Add(observer);
if (_observers.Count == 1)
{
Initialize();
}
return Disposable.Create(() =>
{
_observers.Remove(observer);
if (_observers.Count == 0)
{
Deinitialize();
}
});
}
protected virtual ActivatorListener CreateListener() => new ActivatorListener(this);
protected virtual void Deinitialize()
{ {
Contract.Requires<ArgumentNullException>(observer != null); _activatorSubscription.Dispose();
_activatorSubscription = null;
}
protected virtual void Initialize()
{
_activatorSubscription = Activator.Subscribe(Listener);
}
protected virtual void NotifyCompleted()
{
foreach (var observer in _observers)
{
observer.OnCompleted();
}
Deinitialize();
_observers = null;
}
protected virtual void NotifyError(Exception error)
{
foreach (var observer in _observers)
{
observer.OnError(error);
}
Deinitialize();
_observers = null;
}
protected virtual void NotifyActive(bool active)
{
IsActive = active;
PublishValue();
}
private void PublishValue()
{
if (IsActive.HasValue)
{
var v = IsActive.Value ? Value : AvaloniaProperty.UnsetValue;
if (!Equals(v, _last))
{
foreach (var observer in _observers)
{
observer.OnNext(v);
}
_last = v;
}
}
}
protected class ActivatorListener : IObserver<bool>
{
public ActivatorListener(ActivatedValue parent)
{
Parent = parent;
}
protected ActivatedValue Parent { get; }
return _activator void IObserver<bool>.OnCompleted() => Parent.NotifyCompleted();
.Select(active => active ? Value : AvaloniaProperty.UnsetValue) void IObserver<bool>.OnError(Exception error) => Parent.NotifyError(error);
.Subscribe(observer); void IObserver<bool>.OnNext(bool value) => Parent.NotifyActive(value);
} }
} }
} }

Loading…
Cancel
Save