diff --git a/Perspex.UnitTests/Styling/ActivatorTests.cs b/Perspex.UnitTests/Styling/ActivatorTests.cs index 3e27529bf3..e21abe7500 100644 --- a/Perspex.UnitTests/Styling/ActivatorTests.cs +++ b/Perspex.UnitTests/Styling/ActivatorTests.cs @@ -15,10 +15,10 @@ public class ActivatorTests { [TestMethod] - public void Activator_Should_Follow_Single_Input() + public void Activator_And_Should_Follow_Single_Input() { var inputs = new[] { new TestSubject(false) }; - var target = new Activator(inputs); + var target = new Activator(inputs, ActivatorMode.And); var result = new TestObserver(); target.Subscribe(result); @@ -34,7 +34,7 @@ } [TestMethod] - public void Activator_Should_AND_Multiple_Inputs() + public void Activator_And_Should_AND_Multiple_Inputs() { var inputs = new[] { @@ -42,7 +42,7 @@ new TestSubject(false), new TestSubject(true), }; - var target = new Activator(inputs); + var target = new Activator(inputs, ActivatorMode.And); var result = new TestObserver(); target.Subscribe(result); @@ -59,7 +59,7 @@ } [TestMethod] - public void Activator_Should_Unsubscribe_All_When_Input_Completes_On_False() + public void Activator_And_Should_Unsubscribe_All_When_Input_Completes_On_False() { var inputs = new[] { @@ -67,7 +67,7 @@ new TestSubject(false), new TestSubject(true), }; - var target = new Activator(inputs); + var target = new Activator(inputs, ActivatorMode.And); var result = new TestObserver(); target.Subscribe(result); @@ -85,7 +85,7 @@ } [TestMethod] - public void Activator_Should_Not_Unsubscribe_All_When_Input_Completes_On_False() + public void Activator_And_Should_Not_Unsubscribe_All_When_Input_Completes_On_True() { var inputs = new[] { @@ -93,7 +93,7 @@ new TestSubject(false), new TestSubject(true), }; - var target = new Activator(inputs); + var target = new Activator(inputs, ActivatorMode.And); var result = new TestObserver(); target.Subscribe(result); @@ -105,5 +105,96 @@ Assert.AreEqual(1, inputs[1].SubscriberCount); Assert.AreEqual(1, inputs[2].SubscriberCount); } + + [TestMethod] + public void Activator_Or_Should_Follow_Single_Input() + { + var inputs = new[] { new TestSubject(false) }; + var target = new Activator(inputs, ActivatorMode.Or); + var result = new TestObserver(); + + target.Subscribe(result); + Assert.IsFalse(result.GetValue()); + inputs[0].OnNext(true); + Assert.IsTrue(result.GetValue()); + inputs[0].OnNext(false); + Assert.IsFalse(result.GetValue()); + inputs[0].OnNext(true); + Assert.IsTrue(result.GetValue()); + + Assert.AreEqual(1, inputs[0].SubscriberCount); + } + + [TestMethod] + public void Activator_Or_Should_OR_Multiple_Inputs() + { + var inputs = new[] + { + new TestSubject(false), + new TestSubject(false), + new TestSubject(true), + }; + var target = new Activator(inputs, ActivatorMode.Or); + var result = new TestObserver(); + + target.Subscribe(result); + Assert.IsTrue(result.GetValue()); + inputs[2].OnNext(false); + Assert.IsFalse(result.GetValue()); + inputs[0].OnNext(true); + Assert.IsTrue(result.GetValue()); + + Assert.AreEqual(1, inputs[0].SubscriberCount); + Assert.AreEqual(1, inputs[1].SubscriberCount); + Assert.AreEqual(1, inputs[2].SubscriberCount); + } + + [TestMethod] + public void Activator_Or_Should_Unsubscribe_All_When_Input_Completes_On_True() + { + var inputs = new[] + { + new TestSubject(false), + new TestSubject(false), + new TestSubject(true), + }; + var target = new Activator(inputs, ActivatorMode.Or); + var result = new TestObserver(); + + target.Subscribe(result); + Assert.IsTrue(result.GetValue()); + inputs[2].OnNext(false); + Assert.IsFalse(result.GetValue()); + inputs[0].OnNext(true); + Assert.IsTrue(result.GetValue()); + inputs[0].OnCompleted(); + + Assert.AreEqual(0, inputs[0].SubscriberCount); + Assert.AreEqual(0, inputs[1].SubscriberCount); + Assert.AreEqual(0, inputs[2].SubscriberCount); + } + + [TestMethod] + public void Activator_Or_Should_Not_Unsubscribe_All_When_Input_Completes_On_False() + { + var inputs = new[] + { + new TestSubject(false), + new TestSubject(false), + new TestSubject(true), + }; + var target = new Activator(inputs, ActivatorMode.Or); + var result = new TestObserver(); + + target.Subscribe(result); + Assert.IsTrue(result.GetValue()); + inputs[2].OnNext(false); + Assert.IsFalse(result.GetValue()); + inputs[2].OnCompleted(); + + Assert.AreEqual(1, inputs[0].SubscriberCount); + Assert.AreEqual(1, inputs[1].SubscriberCount); + Assert.AreEqual(1, inputs[2].SubscriberCount); + } } } diff --git a/Perspex/Styling/Activator.cs b/Perspex/Styling/Activator.cs index 80a2fe94db..5b4e5b88c5 100644 --- a/Perspex/Styling/Activator.cs +++ b/Perspex/Styling/Activator.cs @@ -11,8 +11,16 @@ namespace Perspex.Styling using System.Linq; using System.Reactive.Disposables; + public enum ActivatorMode + { + And, + Or, + } + public class Activator : IObservable { + ActivatorMode mode; + List values = new List(); List subscriptions = new List(); @@ -21,10 +29,12 @@ namespace Perspex.Styling bool last = false; - public Activator(IEnumerable> inputs) + public Activator(IEnumerable> inputs, ActivatorMode mode = ActivatorMode.And) { int i = 0; + this.mode = mode; + foreach (IObservable input in inputs) { int iCaptured = i; @@ -53,7 +63,19 @@ namespace Perspex.Styling { this.values[index] = value; - bool current = this.values.All(x => x); + bool current; + + switch (this.mode) + { + case ActivatorMode.And: + current = this.values.All(x => x); + break; + case ActivatorMode.Or: + current = this.values.Any(x => x); + break; + default: + throw new InvalidOperationException("Invalid Activator mode."); + } if (current != last) { @@ -64,10 +86,13 @@ namespace Perspex.Styling private void Finish(int i) { - if (!this.values[i]) + // If the observable has finished on 'false' and we're in And mode then it will never + // go back to true so we can unsubscribe from all the other subscriptions now. + // Similarly in Or mode; if the completed value is true then we're done. + bool unsubscribe = this.mode == ActivatorMode.And ? !this.values[i] : this.values[i]; + + if (unsubscribe) { - // If the observable has finished on 'false' then it will never go back to true - // so we can unsubscribe from all the other subscriptions now. foreach (IDisposable subscription in this.subscriptions) { subscription.Dispose();