12 changed files with 485 additions and 192 deletions
@ -0,0 +1,80 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Perspex.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// An observable which is switched on or off according to an activator observable.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// An <see cref="ActivatedObservable"/> has two inputs: an activator observable a
|
|||
/// <see cref="Source"/> observable which produces the activated value. When the activator
|
|||
/// produces true, the <see cref="ActivatedObservable"/> will produce the current activated
|
|||
/// value. When the activator produces false it will produce
|
|||
/// <see cref="PerspexProperty.UnsetValue"/>.
|
|||
/// </remarks>
|
|||
internal class ActivatedObservable : ObservableBase<object>, IDescription |
|||
{ |
|||
/// <summary>
|
|||
/// The activator.
|
|||
/// </summary>
|
|||
private readonly IObservable<bool> _activator; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
|
|||
/// </summary>
|
|||
/// <param name="activator">The activator.</param>
|
|||
/// <param name="source">An observable that produces the activated value.</param>
|
|||
/// <param name="description">The binding description.</param>
|
|||
public ActivatedObservable( |
|||
IObservable<bool> activator, |
|||
IObservable<object> source, |
|||
string description) |
|||
{ |
|||
_activator = activator; |
|||
Description = description; |
|||
Source = source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a description of the binding.
|
|||
/// </summary>
|
|||
public string Description |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
|
|||
/// </summary>
|
|||
public IObservable<object> Source |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
|
|||
protected override IDisposable SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
|
|||
var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default); |
|||
var activatorCompleted = _activator.TakeLast(1).Select(_ => Unit.Default); |
|||
var completed = sourceCompleted.Merge(activatorCompleted); |
|||
|
|||
return _activator |
|||
.CombineLatest(Source, (x, y) => new { Active = x, Value = y }) |
|||
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) |
|||
.DistinctUntilChanged() |
|||
.TakeUntil(completed) |
|||
.Subscribe(observer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
|
|||
namespace Perspex.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// A subject which is switched on or off according to an activator observable.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// An <see cref="ActivatedSubject"/> has two inputs: an activator observable and either an
|
|||
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
|
|||
/// activated value. When the activator produces true, the <see cref="ActivatedObservable"/> will
|
|||
/// produce the current activated value. When the activator produces false it will produce
|
|||
/// <see cref="PerspexProperty.UnsetValue"/>.
|
|||
/// </remarks>
|
|||
internal class ActivatedSubject : ISubject<object>, IDescription |
|||
{ |
|||
private IObservable<bool> _activator; |
|||
private bool _active; |
|||
private object _pushValue; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ActivatedSubject"/> class.
|
|||
/// </summary>
|
|||
/// <param name="activator">The activator.</param>
|
|||
/// <param name="source">An observable that produces the activated value.</param>
|
|||
/// <param name="description">The binding description.</param>
|
|||
public ActivatedSubject( |
|||
IObservable<bool> activator, |
|||
ISubject<object> source, |
|||
string description) |
|||
{ |
|||
_activator = activator; |
|||
Description = description; |
|||
Source = source; |
|||
|
|||
_activator.Skip(1).Subscribe(ActivatorChanged); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a description of the binding.
|
|||
/// </summary>
|
|||
public string Description |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying subject.
|
|||
/// </summary>
|
|||
public ISubject<object> Source |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies all subscribed observers about the end of the sequence.
|
|||
/// </summary>
|
|||
public void OnCompleted() |
|||
{ |
|||
if (_active) |
|||
{ |
|||
Source.OnCompleted(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies all subscribed observers with the exception.
|
|||
/// </summary>
|
|||
/// <param name="error">The exception to send to all subscribed observers.</param>
|
|||
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
|
|||
public void OnError(Exception error) |
|||
{ |
|||
if (_active) |
|||
{ |
|||
Source.OnError(error); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies all subscribed observers with the value.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to send to all subscribed observers.</param>
|
|||
public void OnNext(object value) |
|||
{ |
|||
_pushValue = value; |
|||
|
|||
if (_active) |
|||
{ |
|||
Source.OnNext(value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
|
|||
public IDisposable Subscribe(IObserver<object> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
|
|||
var completed = _activator.TakeLast(1).Select(_ => Unit.Default); |
|||
|
|||
return _activator |
|||
.CombineLatest(Source, (x, y) => new { Active = x, Value = y }) |
|||
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) |
|||
.DistinctUntilChanged() |
|||
.TakeUntil(completed) |
|||
.Subscribe(observer); |
|||
} |
|||
|
|||
private void ActivatorChanged(bool active) |
|||
{ |
|||
_active = active; |
|||
Source.OnNext(active ? _pushValue : PerspexProperty.UnsetValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Perspex.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// An value which is switched on or off according to an activator observable.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// An <see cref="ActivatedValue"/> has two inputs: an activator observable and an
|
|||
/// <see cref="Value"/>. When the activator produces true, the
|
|||
/// <see cref="ActivatedValue"/> will produce the current value. When the activator
|
|||
/// produces false it will produce <see cref="PerspexProperty.UnsetValue"/>.
|
|||
/// </remarks>
|
|||
internal class ActivatedValue : ObservableBase<object>, IDescription |
|||
{ |
|||
/// <summary>
|
|||
/// The activator.
|
|||
/// </summary>
|
|||
private readonly IObservable<bool> _activator; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
|
|||
/// </summary>
|
|||
/// <param name="activator">The activator.</param>
|
|||
/// <param name="value">The activated value.</param>
|
|||
/// <param name="description">The binding description.</param>
|
|||
public ActivatedValue( |
|||
IObservable<bool> activator, |
|||
object value, |
|||
string description) |
|||
{ |
|||
_activator = activator; |
|||
Value = value; |
|||
Description = description; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the activated value.
|
|||
/// </summary>
|
|||
public object Value |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a description of the binding.
|
|||
/// </summary>
|
|||
public string Description |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
|
|||
protected override IDisposable SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
|
|||
return _activator |
|||
.Select(active => active ? Value : PerspexProperty.UnsetValue) |
|||
.Subscribe(observer); |
|||
} |
|||
} |
|||
} |
|||
@ -1,107 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Perspex.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an observable for a style.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// A <see cref="StyleBinding"/> has two inputs: an activator observable and either an
|
|||
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
|
|||
/// activated value. When the activator produces true, the <see cref="StyleBinding"/> will
|
|||
/// produce the current activated value. When the activator produces false it will produce
|
|||
/// <see cref="PerspexProperty.UnsetValue"/>.
|
|||
/// </remarks>
|
|||
internal class StyleBinding : ObservableBase<object>, IDescription |
|||
{ |
|||
/// <summary>
|
|||
/// The activator.
|
|||
/// </summary>
|
|||
private readonly IObservable<bool> _activator; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
|
|||
/// </summary>
|
|||
/// <param name="activator">The activator.</param>
|
|||
/// <param name="activatedValue">The activated value.</param>
|
|||
/// <param name="description">The binding description.</param>
|
|||
public StyleBinding( |
|||
IObservable<bool> activator, |
|||
object activatedValue, |
|||
string description) |
|||
{ |
|||
_activator = activator; |
|||
ActivatedValue = activatedValue; |
|||
Description = description; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
|
|||
/// </summary>
|
|||
/// <param name="activator">The activator.</param>
|
|||
/// <param name="source">An observable that produces the activated value.</param>
|
|||
/// <param name="description">The binding description.</param>
|
|||
public StyleBinding( |
|||
IObservable<bool> activator, |
|||
IObservable<object> source, |
|||
string description) |
|||
{ |
|||
_activator = activator; |
|||
Description = description; |
|||
Source = source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the activated value.
|
|||
/// </summary>
|
|||
public object ActivatedValue |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a description of the binding.
|
|||
/// </summary>
|
|||
public string Description |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
|
|||
/// </summary>
|
|||
public IObservable<object> Source |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
|
|||
protected override IDisposable SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
|
|||
if (Source == null) |
|||
{ |
|||
return _activator |
|||
.Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue) |
|||
.Subscribe(observer); |
|||
} |
|||
else |
|||
{ |
|||
return _activator |
|||
.CombineLatest(Source, (x, y) => new { Active = x, Value = y }) |
|||
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) |
|||
.Subscribe(observer); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Styling.UnitTests |
|||
{ |
|||
public class ActivatedObservableTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Produce_Correct_Values() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var source = new BehaviorSubject<object>(1); |
|||
var target = new ActivatedObservable(activator, source, string.Empty); |
|||
var result = new List<object>(); |
|||
|
|||
target.Subscribe(x => result.Add(x)); |
|||
|
|||
activator.OnNext(true); |
|||
source.OnNext(2); |
|||
activator.OnNext(false); |
|||
source.OnNext(3); |
|||
activator.OnNext(true); |
|||
|
|||
Assert.Equal( |
|||
new[] |
|||
{ |
|||
PerspexProperty.UnsetValue, |
|||
1, |
|||
2, |
|||
PerspexProperty.UnsetValue, |
|||
3, |
|||
}, |
|||
result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Complete_When_Source_Completes() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var source = new BehaviorSubject<object>(1); |
|||
var target = new ActivatedObservable(activator, source, string.Empty); |
|||
var completed = false; |
|||
|
|||
target.Subscribe(_ => { }, () => completed = true); |
|||
source.OnCompleted(); |
|||
|
|||
Assert.True(completed); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Complete_When_Activator_Completes() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var source = new BehaviorSubject<object>(1); |
|||
var target = new ActivatedObservable(activator, source, string.Empty); |
|||
var completed = false; |
|||
|
|||
target.Subscribe(_ => { }, () => completed = true); |
|||
activator.OnCompleted(); |
|||
|
|||
Assert.True(completed); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Reactive.Subjects; |
|||
using Perspex.Data; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Styling.UnitTests |
|||
{ |
|||
public class ActivatedSubjectTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Set_Values() |
|||
{ |
|||
var data = new Class1 { Foo = "foo" }; |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var source = data.GetSubject( |
|||
(PerspexProperty)Class1.FooProperty, |
|||
BindingPriority.LocalValue); |
|||
var target = new ActivatedSubject(activator, source, string.Empty); |
|||
|
|||
target.OnNext("bar"); |
|||
Assert.Equal("foo", data.Foo); |
|||
activator.OnNext(true); |
|||
target.OnNext("baz"); |
|||
Assert.Equal("baz", data.Foo); |
|||
activator.OnNext(false); |
|||
Assert.Equal("foo", data.Foo); |
|||
target.OnNext("bax"); |
|||
activator.OnNext(true); |
|||
Assert.Equal("bax", data.Foo); |
|||
} |
|||
|
|||
private class Class1 : PerspexObject |
|||
{ |
|||
public static readonly PerspexProperty<string> FooProperty = |
|||
PerspexProperty.Register<Class1, string>("Foo", "foodefault"); |
|||
|
|||
public string Foo |
|||
{ |
|||
get { return GetValue(FooProperty); } |
|||
set { SetValue(FooProperty, value); } |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Styling.UnitTests |
|||
{ |
|||
public class ActivatedValueTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Produce_Correct_Values() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var target = new ActivatedValue(activator, 1, string.Empty); |
|||
var result = new List<object>(); |
|||
|
|||
target.Subscribe(x => result.Add(x)); |
|||
|
|||
activator.OnNext(true); |
|||
activator.OnNext(false); |
|||
|
|||
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Complete_When_Activator_Completes() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var target = new ActivatedValue(activator, 1, string.Empty); |
|||
var completed = false; |
|||
|
|||
target.Subscribe(_ => { }, () => completed = true); |
|||
activator.OnCompleted(); |
|||
|
|||
Assert.True(completed); |
|||
} |
|||
} |
|||
} |
|||
@ -1,79 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Styling.UnitTests |
|||
{ |
|||
public class StyleBindingTests |
|||
{ |
|||
[Fact] |
|||
public async void Should_Produce_UnsetValue_On_Activator_False() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var target = new StyleBinding(activator, 1, string.Empty); |
|||
var result = await target.Take(1); |
|||
|
|||
Assert.Equal(PerspexProperty.UnsetValue, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async void Should_Produce_Value_On_Activator_True() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(true); |
|||
var target = new StyleBinding(activator, 1, string.Empty); |
|||
var result = await target.Take(1); |
|||
|
|||
Assert.Equal(1, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Change_Value_On_Activator_Change() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var target = new StyleBinding(activator, 1, string.Empty); |
|||
var result = new List<object>(); |
|||
|
|||
target.Subscribe(x => result.Add(x)); |
|||
|
|||
activator.OnNext(true); |
|||
activator.OnNext(false); |
|||
|
|||
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Change_Value_With_Source_Observable() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var source = new BehaviorSubject<object>(1); |
|||
var target = new StyleBinding(activator, source, string.Empty); |
|||
var result = new List<object>(); |
|||
|
|||
target.Subscribe(x => result.Add(x)); |
|||
|
|||
activator.OnNext(true); |
|||
source.OnNext(2); |
|||
activator.OnNext(false); |
|||
|
|||
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Complete_When_Activator_Completes() |
|||
{ |
|||
var activator = new BehaviorSubject<bool>(false); |
|||
var target = new StyleBinding(activator, 1, string.Empty); |
|||
var completed = false; |
|||
|
|||
target.Subscribe(_ => { }, () => completed = true); |
|||
activator.OnCompleted(); |
|||
|
|||
Assert.True(completed); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue