Browse Source

More work on styling system.

pull/4/head
grokys 12 years ago
parent
commit
4e09447720
  1. 1
      Perspex.UnitTests/Perspex.UnitTests.csproj
  2. 12
      Perspex.UnitTests/PerspexObjectTests.cs
  3. 2
      Perspex.UnitTests/PerspexPropertyTests.cs
  4. 91
      Perspex.UnitTests/Style.cs
  5. 111
      Perspex/PerspexObject.cs
  6. 15
      Perspex/PerspexProperty.cs
  7. 50
      Perspex/Setter.cs
  8. 44
      Perspex/Style.cs

1
Perspex.UnitTests/Perspex.UnitTests.csproj

@ -65,7 +65,6 @@
<Compile Include="PerspexPropertyTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PerspexObjectTests.cs" />
<Compile Include="Style.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Perspex\Perspex.csproj">

12
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()
{

2
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);
}

91
Perspex.UnitTests/Style.cs

@ -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<Setter>();
}
public Func<Control, Match> Selector
{
get;
set;
}
public IEnumerable<Setter> Setters
{
get;
set;
}
public void Attach(Control control)
{
Match match = this.Selector(control);
if (match != null)
{
List<IObservable<bool>> o = new List<IObservable<bool>>();
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;
}
}
}
}

111
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
/// <summary>
/// The current bindings on this object.
/// </summary>
private Dictionary<PerspexProperty, IDisposable> bindings =
new Dictionary<PerspexProperty, IDisposable>();
private Dictionary<PerspexProperty, Binding> bindings =
new Dictionary<PerspexProperty, Binding>();
/// <summary>
/// Raised when a <see cref="PerspexProperty"/> value changes on this object/
@ -161,17 +162,17 @@ namespace Perspex
public void ClearBinding(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
IDisposable binding;
Binding binding;
if (this.bindings.TryGetValue(property, out binding))
{
binding.Dispose();
binding.Dispose.Dispose();
this.bindings.Remove(property);
}
}
/// <summary>
/// Clears a <see cref="PerspexProperty"/> value, including its bindings.
/// Clears a <see cref="PerspexProperty"/> value, including its binding.
/// </summary>
/// <param name="property">The property.</param>
public void ClearValue(PerspexProperty property)
@ -181,6 +182,46 @@ namespace Perspex
this.values.Remove(property);
}
/// <summary>
/// Clears a binding on a <see cref="PerspexProperty"/>, returning the bound observable and
/// leaving the last bound value in place.
/// </summary>
/// <param name="property">The property.</param>
public IObservable<object> ExtractBinding(PerspexProperty property)
{
Binding binding;
if (this.bindings.TryGetValue(property, out binding))
{
this.bindings.Remove(property);
return (IObservable<object>)binding.Observable;
}
else
{
return null;
}
}
/// <summary>
/// Clears a binding on a <see cref="PerspexProperty"/>, returning the bound observable and
/// leaving the last bound value in place.
/// </summary>
/// <param name="property">The property.</param>
public IObservable<T> ExtractBinding<T>(PerspexProperty<T> property)
{
Binding binding;
if (this.bindings.TryGetValue(property, out binding))
{
this.bindings.Remove(property);
return (IObservable<T>)binding.Observable;
}
else
{
return null;
}
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
@ -335,8 +376,40 @@ namespace Perspex
{
Contract.Requires<NullReferenceException>(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<object> observable = value as IObservable<object>;
if (observable == null)
{
MethodInfo cast = typeof(PerspexObject).GetTypeInfo()
.DeclaredMethods
.FirstOrDefault(x => x.Name == "CastToObject")
.MakeGenericMethod(observableType.GenericTypeArguments[0]);
observable = (IObservable<object>)cast.Invoke(null, new[] { value });
}
this.bindings.Add(property, new Binding
{
Observable = value,
Dispose = observable.Subscribe(x =>
{
this.SetValueImpl(property, x);
}),
});
}
}
/// <summary>
@ -362,14 +435,18 @@ namespace Perspex
{
Contract.Requires<NullReferenceException>(property != null);
this.ClearBinding(property);
this.SetValue((PerspexProperty)property, source);
}
IDisposable binding = source.Subscribe(value =>
private static IObservable<object> CastToObject<T>(IObservable<T> observable)
{
return Observable.Create<object>(observer =>
{
this.SetValueImpl(property, value);
return observable.Subscribe(value =>
{
observer.OnNext(value);
});
});
this.bindings.Add(property, binding);
}
/// <summary>
@ -412,6 +489,11 @@ namespace Perspex
{
Contract.Requires<NullReferenceException>(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; }
}
}
}

15
Perspex/PerspexProperty.cs

@ -47,7 +47,7 @@ namespace Perspex
Contract.Requires<NullReferenceException>(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
/// <summary>
/// Gets the type of the property's value.
/// </summary>
public Type ValueType { get; private set; }
public Type PropertyType { get; private set; }
/// <summary>
/// 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());
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>

50
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<object>
{
private Control control;
private object onValue;
private object offValue;
private List<IObserver<object>> observers;
public SetterSubject(Control control, object onValue, object offValue)
{
this.control = control;
this.onValue = onValue;
this.offValue = offValue;
this.observers = new List<IObserver<object>>();
}
public IDisposable Subscribe(IObserver<object> observer)
{
observers.Add(observer);
return Disposable.Create(() => this.observers.Remove(observer));
}
public void Push(bool on)
{
foreach (IObserver<object> 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<NullReferenceException>(control != null);
this.oldValue = control.GetValue(this.Property);
control.SetValue(this.Property, this.Value);
}
public void Unapply(Control control)
{
Contract.Requires<NullReferenceException>(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);
}
}
}

44
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<SetterSubject> subjects = new List<SetterSubject>();
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);
}
});
}
}
}

Loading…
Cancel
Save