From 4e094477206d8e5780076b86fa5e2a402b992851 Mon Sep 17 00:00:00 2001 From: grokys Date: Sun, 2 Feb 2014 04:51:09 +0100 Subject: [PATCH] More work on styling system. --- Perspex.UnitTests/Perspex.UnitTests.csproj | 1 - Perspex.UnitTests/PerspexObjectTests.cs | 12 +++ Perspex.UnitTests/PerspexPropertyTests.cs | 2 +- Perspex.UnitTests/Style.cs | 91 ----------------- Perspex/PerspexObject.cs | 111 +++++++++++++++++++-- Perspex/PerspexProperty.cs | 15 ++- Perspex/Setter.cs | 50 +++++++--- Perspex/Style.cs | 44 +++----- 8 files changed, 177 insertions(+), 149 deletions(-) delete mode 100644 Perspex.UnitTests/Style.cs diff --git a/Perspex.UnitTests/Perspex.UnitTests.csproj b/Perspex.UnitTests/Perspex.UnitTests.csproj index 4fe2bb0fa6..33ff73f5aa 100644 --- a/Perspex.UnitTests/Perspex.UnitTests.csproj +++ b/Perspex.UnitTests/Perspex.UnitTests.csproj @@ -65,7 +65,6 @@ - diff --git a/Perspex.UnitTests/PerspexObjectTests.cs b/Perspex.UnitTests/PerspexObjectTests.cs index 8889fb2a05..b222fbc760 100644 --- a/Perspex.UnitTests/PerspexObjectTests.cs +++ b/Perspex.UnitTests/PerspexObjectTests.cs @@ -243,6 +243,18 @@ namespace Perspex.UnitTests Assert.AreEqual("initial", target.GetValue(Class1.FooProperty)); } + [TestMethod] + public void Binding_NonGeneric_Sets_Current_Value() + { + Class1 target = new Class1(); + Class1 source = new Class1(); + + source.SetValue(Class1.FooProperty, "initial"); + target.SetValue((PerspexProperty)Class1.FooProperty, source.GetObservable(Class1.FooProperty)); + + Assert.AreEqual("initial", target.GetValue(Class1.FooProperty)); + } + [TestMethod] public void Binding_Sets_Subsequent_Value() { diff --git a/Perspex.UnitTests/PerspexPropertyTests.cs b/Perspex.UnitTests/PerspexPropertyTests.cs index 3e1a24e6f2..76e4f5873c 100644 --- a/Perspex.UnitTests/PerspexPropertyTests.cs +++ b/Perspex.UnitTests/PerspexPropertyTests.cs @@ -22,7 +22,7 @@ namespace Perspex.UnitTests false); Assert.AreEqual("test", target.Name); - Assert.AreEqual(typeof(string), target.ValueType); + Assert.AreEqual(typeof(string), target.PropertyType); Assert.AreEqual(typeof(Class1), target.OwnerType); Assert.AreEqual(false, target.Inherits); } diff --git a/Perspex.UnitTests/Style.cs b/Perspex.UnitTests/Style.cs deleted file mode 100644 index 653e69defc..0000000000 --- a/Perspex.UnitTests/Style.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Text; -using System.Threading.Tasks; -using Perspex.Controls; - -namespace Perspex -{ - public class Style - { - private bool applied; - - public Style() - { - this.Setters = new List(); - } - - public Func Selector - { - get; - set; - } - - public IEnumerable Setters - { - get; - set; - } - - public void Attach(Control control) - { - Match match = this.Selector(control); - - if (match != null) - { - List> o = new List>(); - - while (match != null) - { - if (match.Observable != null) - { - o.Add(match.Observable); - } - - match = match.Previous; - } - - Observable.CombineLatest(o).Subscribe(x => - { - if (x.All(y => y)) - { - this.Apply(control); - } - else if (this.applied) - { - this.Unapply(control); - } - }); - } - } - - private void Apply(Control control) - { - if (this.Setters != null) - { - foreach (Setter setter in this.Setters) - { - setter.Apply(control); - } - } - - this.applied = true; - } - - private void Unapply(Control control) - { - if (this.Setters != null) - { - foreach (Setter setter in this.Setters) - { - setter.Unapply(control); - } - - this.applied = false; - } - } - } -} diff --git a/Perspex/PerspexObject.cs b/Perspex/PerspexObject.cs index 9075e1ad80..dc250c08b5 100644 --- a/Perspex/PerspexObject.cs +++ b/Perspex/PerspexObject.cs @@ -10,6 +10,7 @@ namespace Perspex using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; + using System.Linq.Expressions; using System.Reactive; using System.Reactive.Linq; using System.Reflection; @@ -42,8 +43,8 @@ namespace Perspex /// /// The current bindings on this object. /// - private Dictionary bindings = - new Dictionary(); + private Dictionary bindings = + new Dictionary(); /// /// Raised when a value changes on this object/ @@ -161,17 +162,17 @@ namespace Perspex public void ClearBinding(PerspexProperty property) { Contract.Requires(property != null); - IDisposable binding; + Binding binding; if (this.bindings.TryGetValue(property, out binding)) { - binding.Dispose(); + binding.Dispose.Dispose(); this.bindings.Remove(property); } } /// - /// Clears a value, including its bindings. + /// Clears a value, including its binding. /// /// The property. public void ClearValue(PerspexProperty property) @@ -181,6 +182,46 @@ namespace Perspex this.values.Remove(property); } + /// + /// Clears a binding on a , returning the bound observable and + /// leaving the last bound value in place. + /// + /// The property. + public IObservable ExtractBinding(PerspexProperty property) + { + Binding binding; + + if (this.bindings.TryGetValue(property, out binding)) + { + this.bindings.Remove(property); + return (IObservable)binding.Observable; + } + else + { + return null; + } + } + + /// + /// Clears a binding on a , returning the bound observable and + /// leaving the last bound value in place. + /// + /// The property. + public IObservable ExtractBinding(PerspexProperty property) + { + Binding binding; + + if (this.bindings.TryGetValue(property, out binding)) + { + this.bindings.Remove(property); + return (IObservable)binding.Observable; + } + else + { + return null; + } + } + /// /// Gets an observable for a . /// @@ -335,8 +376,40 @@ namespace Perspex { Contract.Requires(property != null); + TypeInfo typeInfo = value.GetType().GetTypeInfo(); + Type observableType = typeInfo.ImplementedInterfaces.FirstOrDefault(x => + x.IsConstructedGenericType && + x.GetGenericTypeDefinition() == typeof(IObservable<>)); + this.ClearBinding(property); - this.SetValueImpl(property, value); + + if (observableType == null) + { + this.SetValueImpl(property, value); + } + else + { + IObservable observable = value as IObservable; + + if (observable == null) + { + MethodInfo cast = typeof(PerspexObject).GetTypeInfo() + .DeclaredMethods + .FirstOrDefault(x => x.Name == "CastToObject") + .MakeGenericMethod(observableType.GenericTypeArguments[0]); + + observable = (IObservable)cast.Invoke(null, new[] { value }); + } + + this.bindings.Add(property, new Binding + { + Observable = value, + Dispose = observable.Subscribe(x => + { + this.SetValueImpl(property, x); + }), + }); + } } /// @@ -362,14 +435,18 @@ namespace Perspex { Contract.Requires(property != null); - this.ClearBinding(property); + this.SetValue((PerspexProperty)property, source); + } - IDisposable binding = source.Subscribe(value => + private static IObservable CastToObject(IObservable observable) + { + return Observable.Create(observer => { - this.SetValueImpl(property, value); + return observable.Subscribe(value => + { + observer.OnNext(value); + }); }); - - this.bindings.Add(property, binding); } /// @@ -412,6 +489,11 @@ namespace Perspex { Contract.Requires(property != null); + if (!property.IsValidType(value)) + { + throw new InvalidOperationException("Invalid value for " + property.Name); + } + object oldValue = this.GetValue(property); if (!object.Equals(oldValue, value)) @@ -420,5 +502,12 @@ namespace Perspex this.RaisePropertyChanged(property, oldValue, value); } } + + private class Binding + { + public object Observable { get; set; } + + public IDisposable Dispose { get; set; } + } } } diff --git a/Perspex/PerspexProperty.cs b/Perspex/PerspexProperty.cs index 6ec6856ad4..5b1f8e5729 100644 --- a/Perspex/PerspexProperty.cs +++ b/Perspex/PerspexProperty.cs @@ -47,7 +47,7 @@ namespace Perspex Contract.Requires(ownerType != null); this.Name = name; - this.ValueType = valueType; + this.PropertyType = valueType; this.OwnerType = ownerType; this.Inherits = inherits; this.defaultValues.Add(ownerType, defaultValue); @@ -61,7 +61,7 @@ namespace Perspex /// /// Gets the type of the property's value. /// - public Type ValueType { get; private set; } + public Type PropertyType { get; private set; } /// /// Gets the type of the class that registers the property. @@ -125,6 +125,17 @@ namespace Perspex return this.defaultValues[this.OwnerType]; } + public bool IsValidType(object value) + { + if (value == null) + { + return !this.PropertyType.GetTypeInfo().IsValueType || + Nullable.GetUnderlyingType(this.PropertyType) != null; + } + + return this.PropertyType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo()); + } + /// /// Gets the default value for the property on the specified type. /// diff --git a/Perspex/Setter.cs b/Perspex/Setter.cs index bcf758c275..e9e1743dd2 100644 --- a/Perspex/Setter.cs +++ b/Perspex/Setter.cs @@ -7,9 +7,44 @@ namespace Perspex { using System; + using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.Reactive.Disposables; using Perspex.Controls; + internal class SetterSubject : IObservable + { + private Control control; + + private object onValue; + + private object offValue; + + private List> observers; + + public SetterSubject(Control control, object onValue, object offValue) + { + this.control = control; + this.onValue = onValue; + this.offValue = offValue; + this.observers = new List>(); + } + + public IDisposable Subscribe(IObserver observer) + { + observers.Add(observer); + return Disposable.Create(() => this.observers.Remove(observer)); + } + + public void Push(bool on) + { + foreach (IObserver o in this.observers) + { + o.OnNext(on ? this.onValue : this.offValue); + } + } + } + public class Setter { private object oldValue; @@ -26,19 +61,10 @@ namespace Perspex set; } - public void Apply(Control control) + internal SetterSubject CreateSubject(Control control) { - Contract.Requires(control != null); - - this.oldValue = control.GetValue(this.Property); - control.SetValue(this.Property, this.Value); - } - - public void Unapply(Control control) - { - Contract.Requires(control != null); - - control.SetValue(this.Property, this.oldValue); + object oldValue = control.ExtractBinding(this.Property) ?? control.GetValue(this.Property); + return new SetterSubject(control, this.Value, oldValue); } } } diff --git a/Perspex/Style.cs b/Perspex/Style.cs index fec4a09324..f56e8ba25a 100644 --- a/Perspex/Style.cs +++ b/Perspex/Style.cs @@ -10,6 +10,7 @@ namespace Perspex using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; + using System.Reactive.Subjects; using Perspex.Controls; public class Style @@ -51,43 +52,24 @@ namespace Perspex match = match.Previous; } - Observable.CombineLatest(o).Subscribe(x => - { - if (x.All(y => y)) - { - this.Apply(control); - } - else if (this.applied) - { - this.Unapply(control); - } - }); - } - } - - private void Apply(Control control) - { - if (this.Setters != null) - { + List subjects = new List(); + foreach (Setter setter in this.Setters) { - setter.Apply(control); + SetterSubject subject = setter.CreateSubject(control); + subjects.Add(subject); + control.SetValue(setter.Property, subject); } - } - - this.applied = true; - } - private void Unapply(Control control) - { - if (this.Setters != null) - { - foreach (Setter setter in this.Setters) + Observable.CombineLatest(o).Subscribe(x => { - setter.Unapply(control); - } + bool on = x.All(y => y); - this.applied = false; + foreach (SetterSubject subject in subjects) + { + subject.Push(on); + } + }); } } }