diff --git a/Perspex.UnitTests/Perspex.UnitTests.csproj b/Perspex.UnitTests/Perspex.UnitTests.csproj
index b07df6a6e8..2996a1fe0b 100644
--- a/Perspex.UnitTests/Perspex.UnitTests.csproj
+++ b/Perspex.UnitTests/Perspex.UnitTests.csproj
@@ -69,6 +69,8 @@
+
+
diff --git a/Perspex.UnitTests/StyleTests.cs b/Perspex.UnitTests/StyleTests.cs
index be88fa4f97..f842c2563a 100644
--- a/Perspex.UnitTests/StyleTests.cs
+++ b/Perspex.UnitTests/StyleTests.cs
@@ -18,7 +18,7 @@ namespace Perspex.UnitTests
[TestMethod]
public void Style_With_Only_Type_Selector_Should_Update_Value()
{
- Style style = new Style(x => x.Select())
+ Style style = new Style(x => x.Select().OfType())
{
Setters = new[]
{
@@ -38,7 +38,7 @@ namespace Perspex.UnitTests
[TestMethod]
public void Style_With_Class_Selector_Should_Update_And_Restore_Value()
{
- Style style = new Style(x => x.Select().Class("foo"))
+ Style style = new Style(x => x.Select().OfType().Class("foo"))
{
Setters = new[]
{
@@ -62,7 +62,7 @@ namespace Perspex.UnitTests
[TestMethod]
public void Later_Styles_Should_Override_Earlier()
{
- Style style1 = new Style(x => x.Select().Class("foo"))
+ Style style1 = new Style(x => x.Select().OfType().Class("foo"))
{
Setters = new[]
{
@@ -70,7 +70,7 @@ namespace Perspex.UnitTests
},
};
- Style style2 = new Style(x => x.Select().Class("foo"))
+ Style style2 = new Style(x => x.Select().OfType().Class("foo"))
{
Setters = new[]
{
diff --git a/Perspex.UnitTests/Styling/SelectorTests.cs b/Perspex.UnitTests/Styling/SelectorTests.cs
new file mode 100644
index 0000000000..45ec6294a1
--- /dev/null
+++ b/Perspex.UnitTests/Styling/SelectorTests.cs
@@ -0,0 +1,68 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.UnitTests.Styling
+{
+ using System;
+ using System.Linq;
+ using System.Reactive;
+ using System.Reactive.Linq;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Perspex.Styling;
+ using Match = Perspex.Styling.Match;
+
+ [TestClass]
+ public class SelectorTests
+ {
+ [TestMethod]
+ public void OfType_Matches_Control_Of_Correct_Type()
+ {
+ var control = new Class1();
+ var target = control.Select().OfType();
+
+ CollectionAssert.AreEqual(new[] { true }, target.GetActivator().Take(1).ToEnumerable().ToArray());
+ }
+
+ [TestMethod]
+ public void OfType_Doesnt_Match_Control_Of_Wrong_Type()
+ {
+ var control = new Class2();
+ var target = control.Select().OfType();
+
+ CollectionAssert.AreEqual(new[] { false }, target.GetActivator().Take(1).ToEnumerable().ToArray());
+ }
+
+ [TestMethod]
+ public void When_OfType_Matches_Control_Other_Selectors_Are_Subscribed()
+ {
+ var control = new Class1();
+ var target = control.Select().OfType().SubscribeCheck();
+
+ var result = target.GetActivator().ToEnumerable().Take(1).ToArray();
+
+ Assert.AreEqual(1, control.SubscribeCheckObservable.SubscribedCount);
+ }
+
+ [TestMethod]
+ public void When_OfType_Doesnt_Match_Control_Other_Selectors_Are_Not_Subscribed()
+ {
+ var control = new Class1();
+ var target = control.Select().OfType().SubscribeCheck();
+
+ var result = target.GetActivator().ToEnumerable().Take(1).ToArray();
+
+ Assert.AreEqual(0, control.SubscribeCheckObservable.SubscribedCount);
+ }
+
+ public class Class1 : SubscribeCheck
+ {
+ }
+
+ public class Class2 : SubscribeCheck
+ {
+ }
+ }
+}
diff --git a/Perspex.UnitTests/Styling/SubscribeCheck.cs b/Perspex.UnitTests/Styling/SubscribeCheck.cs
new file mode 100644
index 0000000000..e6cd0cde76
--- /dev/null
+++ b/Perspex.UnitTests/Styling/SubscribeCheck.cs
@@ -0,0 +1,59 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.UnitTests.Styling
+{
+ using System;
+ using System.Reactive.Disposables;
+ using Perspex.Styling;
+ using Match = Perspex.Styling.Match;
+
+ public class TestObservable : IObservable
+ {
+ public int SubscribedCount { get; private set; }
+
+ public IDisposable Subscribe(IObserver observer)
+ {
+ ++this.SubscribedCount;
+ observer.OnNext(true);
+ return Disposable.Create(() => --this.SubscribedCount);
+ }
+ }
+
+ public class SubscribeCheck : IStyleable
+ {
+ public SubscribeCheck()
+ {
+ this.Classes = new PerspexList();
+ this.SubscribeCheckObservable = new TestObservable();
+ }
+
+ public PerspexList Classes
+ {
+ get;
+ private set;
+ }
+
+ public TestObservable SubscribeCheckObservable
+ {
+ get;
+ private set;
+ }
+
+ public virtual void SetValue(PerspexProperty property, object value, IObservable activator)
+ {
+ }
+ }
+
+ public static class TestSelectors
+ {
+ public static Match SubscribeCheck(this Match match)
+ {
+ match.Observables.Add(((SubscribeCheck)match.Control).SubscribeCheckObservable);
+ return match;
+ }
+ }
+}
diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj
index ed8045927f..8417ba0679 100644
--- a/Perspex/Perspex.csproj
+++ b/Perspex/Perspex.csproj
@@ -79,6 +79,7 @@
+
diff --git a/Perspex/Styling/Activator.cs b/Perspex/Styling/Activator.cs
new file mode 100644
index 0000000000..d8739ae709
--- /dev/null
+++ b/Perspex/Styling/Activator.cs
@@ -0,0 +1,89 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Styling
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Reactive.Disposables;
+
+ public class Activator : IObservable
+ {
+ List values = new List();
+
+ List subscriptions = new List();
+
+ List> observers = new List>();
+
+ bool last = false;
+
+ public Activator(IList> observables)
+ {
+ int i = 0;
+
+ foreach (IObservable o in observables.Reverse())
+ {
+ int iCaptured = i;
+
+ this.values.Add(false);
+
+ IDisposable subscription = o.Subscribe(
+ x => this.Update(iCaptured, x),
+ x => this.Finish(iCaptured),
+ () => this.Finish(iCaptured));
+
+ this.subscriptions.Add(subscription);
+
+ ++i;
+ }
+ }
+
+ public IDisposable Subscribe(IObserver observer)
+ {
+ Contract.Requires(observer != null);
+
+ this.observers.Add(observer);
+ observer.OnNext(last);
+ return Disposable.Create(() => this.observers.Remove(observer));
+ }
+
+ private void Update(int index, bool value)
+ {
+ this.values[index] = value;
+
+ bool current = this.values.All(x => x);
+
+ if (current != last)
+ {
+ this.Push(current);
+ last = current;
+ }
+ }
+
+ private void Finish(int i)
+ {
+ if (!this.values[i])
+ {
+ // 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();
+ }
+ }
+ }
+
+ private void Push(bool value)
+ {
+ foreach (IObserver observer in this.observers)
+ {
+ observer.OnNext(value);
+ }
+ }
+ }
+}
diff --git a/Perspex/Styling/Match.cs b/Perspex/Styling/Match.cs
index fe74a97576..a05cf66010 100644
--- a/Perspex/Styling/Match.cs
+++ b/Perspex/Styling/Match.cs
@@ -14,54 +14,38 @@ namespace Perspex.Styling
public class Match
{
- public IStyleable Control
+ public Match(IStyleable control)
{
- get;
- set;
+ this.Control = control;
+ this.Observables = new List>();
}
- public IObservable Observable
+ public IStyleable Control
{
get;
- set;
+ private set;
}
- public Match Previous
+ public List> Observables
{
get;
set;
}
- public string Token
+ public string SelectorString
{
get;
set;
}
- public IObservable GetActivator()
+ public Activator GetActivator()
{
- List> observables = new List>();
- Match match = this;
-
- do
- {
- if (match.Observable != null)
- {
- observables.Add(match.Observable);
- }
-
- match = match.Previous;
- }
- while (match != null);
-
- return System.Reactive.Linq.Observable.CombineLatest(observables).Select(x => x.All(b => b));
+ return new Activator(this.Observables);
}
public override string ToString()
{
- string result = (this.Previous != null) ? this.Previous.ToString() : string.Empty;
- result += this.Token;
- return result;
+ return this.SelectorString;
}
}
}
diff --git a/Perspex/Styling/Selectors.cs b/Perspex/Styling/Selectors.cs
index bc8e071a22..f7994dde95 100644
--- a/Perspex/Styling/Selectors.cs
+++ b/Perspex/Styling/Selectors.cs
@@ -18,47 +18,33 @@ namespace Perspex.Styling
public static class Selectors
{
- public static Match Select(this IStyleable control)
+ public static Match Select(this IStyleable control)
{
Contract.Requires(control != null);
- if (control is T)
- {
- return new Match
- {
- Control = control,
- Observable = Observable.Return(true),
- Token = typeof(T).Name,
- };
- }
- else
- {
- return null;
- }
+ return new Match(control);
}
- public static Match Class(this Match selector, string name)
+ public static Match Class(this Match match, string name)
{
+ Contract.Requires(match != null);
Contract.Requires(name != null);
- if (selector != null)
- {
- IObservable match = Observable
- .Return(selector.Control.Classes.Contains(name))
- .Concat(selector.Control.Classes.Changed.Select(e => selector.Control.Classes.Contains(name)));
+ match.Observables.Add(Observable
+ .Return(match.Control.Classes.Contains(name))
+ .Concat(match.Control.Classes.Changed.Select(e => match.Control.Classes.Contains(name))));
+ match.SelectorString += (name[0] == ':') ? name : '.' + name;
- return new Match
- {
- Control = selector.Control,
- Observable = match,
- Previous = selector,
- Token = (name[0] == ':') ? name : '.' + name,
- };
- }
- else
- {
- return null;
- }
+ return match;
+ }
+
+ public static Match OfType(this Match match)
+ {
+ Contract.Requires(match != null);
+
+ match.Observables.Add(Observable.Return(match.Control is T));
+ match.SelectorString += typeof(T).Name;
+ return match;
}
}
}
diff --git a/Perspex/Themes/Default/ButtonStyle.cs b/Perspex/Themes/Default/ButtonStyle.cs
index f04990d3e8..923dfd010a 100644
--- a/Perspex/Themes/Default/ButtonStyle.cs
+++ b/Perspex/Themes/Default/ButtonStyle.cs
@@ -15,7 +15,7 @@ namespace Perspex.Themes.Default
{
this.AddRange(new[]
{
- new Style(x => x.Select