diff --git a/Perspex.UnitTests/Styling/SubscribeCheck.cs b/Perspex.UnitTests/Styling/SubscribeCheck.cs index e6cd0cde76..ed9ba74724 100644 --- a/Perspex.UnitTests/Styling/SubscribeCheck.cs +++ b/Perspex.UnitTests/Styling/SubscribeCheck.cs @@ -27,11 +27,11 @@ namespace Perspex.UnitTests.Styling { public SubscribeCheck() { - this.Classes = new PerspexList(); + this.Classes = Classes; this.SubscribeCheckObservable = new TestObservable(); } - public PerspexList Classes + public Classes Classes { get; private set; diff --git a/Perspex/Classes.cs b/Perspex/Classes.cs new file mode 100644 index 0000000000..7656461540 --- /dev/null +++ b/Perspex/Classes.cs @@ -0,0 +1,172 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using System.Reactive; + using System.Reactive.Subjects; + + public class Classes : ICollection, INotifyCollectionChanged + { + private List inner; + + private Subject beforeChanged + = new Subject(); + + private Subject changed + = new Subject(); + + private Subject afterChanged + = new Subject(); + + public Classes() + { + this.inner = new List(); + } + + public Classes(params string[] classes) + { + this.inner = new List(classes); + } + + public Classes(IEnumerable classes) + { + this.inner = new List(classes); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public int Count + { + get { return this.inner.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public IObservable BeforeChanged + { + get { return this.beforeChanged; } + } + + public IObservable Changed + { + get { return this.changed; } + } + + public IObservable AfterChanged + { + get { return this.afterChanged; } + } + + public void Add(string item) + { + this.Add(Enumerable.Repeat(item, 1)); + } + + public void Add(params string[] items) + { + this.Add((IEnumerable)items); + } + + public void Add(IEnumerable items) + { + items = items.Except(this.inner); + + NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + items); + + this.beforeChanged.OnNext(e); + this.inner.AddRange(items); + this.RaiseChanged(e); + } + + private void RaiseChanged(NotifyCollectionChangedEventArgs e) + { + if (this.CollectionChanged != null) + { + this.CollectionChanged(this, e); + } + + this.changed.OnNext(e); + this.afterChanged.OnNext(e); + } + + public void Clear() + { + NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Reset); + + this.beforeChanged.OnNext(e); + this.inner.Clear(); + this.RaiseChanged(e); + } + + public bool Contains(string item) + { + return this.inner.Contains(item); + } + + public void CopyTo(string[] array, int arrayIndex) + { + this.inner.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return inner.GetEnumerator(); + } + + public bool Remove(string item) + { + return this.Remove(Enumerable.Repeat(item, 1)); + } + + public bool Remove(params string[] items) + { + return this.Remove((IEnumerable)items); + } + + public bool Remove(IEnumerable items) + { + items = items.Intersect(this.inner); + + if (items.Any()) + { + NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + items); + + this.beforeChanged.OnNext(e); + + foreach (string item in items) + { + this.inner.Remove(item); + } + + this.RaiseChanged(e); + return true; + } + else + { + return false; + } + } + } +} diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index 60672e9fec..eef91b0cbe 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -74,7 +74,10 @@ namespace Perspex.Controls public Control() { - this.Classes = new PerspexList(); + this.Classes = new Classes(); + this.Classes.BeforeChanged.Subscribe(x => this.BeginDeferStyleChanges()); + this.Classes.AfterChanged.Subscribe(x => this.EndDeferStyleChanges()); + this.GetObservableWithHistory(ParentPropertyRW).Subscribe(this.ParentChanged); this.GetObservable(IsMouseOverProperty).Subscribe(x => @@ -140,7 +143,7 @@ namespace Perspex.Controls set { this.SetValue(BorderThicknessProperty, value); } } - public PerspexList Classes + public Classes Classes { get; private set; diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index 8417ba0679..66d646050d 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -69,6 +69,7 @@ + diff --git a/Perspex/PerspexObject.cs b/Perspex/PerspexObject.cs index 40c629a539..6ff8753da6 100644 --- a/Perspex/PerspexObject.cs +++ b/Perspex/PerspexObject.cs @@ -12,6 +12,7 @@ namespace Perspex using System.Linq; using System.Linq.Expressions; using System.Reactive; + using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reflection; using Splat; @@ -94,6 +95,40 @@ namespace Perspex } } + /// + /// Defers property updates due to style changes until + /// is called. + /// + public void BeginDeferStyleChanges() + { + foreach (PriorityValue v in this.values.Values) + { + v.BeginDeferStyleChanges(); + } + + this.Log().Debug(string.Format( + "Defer style changes on {0} (#{1:x8})", + this.GetType().Name, + this.GetHashCode())); + } + + /// + /// Ends the defer of property updates due to style changes initiated by a previous call + /// to . + /// + public void EndDeferStyleChanges() + { + foreach (PriorityValue v in this.values.Values) + { + v.EndDeferStyleChanges(); + } + + this.Log().Debug(string.Format( + "End defer style changes on {0} (#{1:x8})", + this.GetType().Name, + this.GetHashCode())); + } + /// /// Gets all s registered on a type. /// diff --git a/Perspex/PriorityValue.cs b/Perspex/PriorityValue.cs index d024d79fbc..95c2153376 100644 --- a/Perspex/PriorityValue.cs +++ b/Perspex/PriorityValue.cs @@ -25,6 +25,10 @@ namespace Perspex private List>> observers = new List>>(); + private int defer; + + private bool dirty; + public object LocalValue { get @@ -113,18 +117,41 @@ namespace Perspex return Disposable.Create(() => this.observers.Remove(observer)); } - private void Push() + public void BeginDeferStyleChanges() { - object value = this.GetEffectiveValue(); + if (this.defer++ == 0) + { + this.dirty = false; + } + } - if (!object.Equals(this.lastValue, value)) + public void EndDeferStyleChanges() + { + if (this.defer > 0 && --this.defer == 0 && dirty) { - foreach (IObserver> observer in this.observers) + this.Push(); + } + } + + private void Push() + { + if (defer == 0) + { + object value = this.GetEffectiveValue(); + + if (!object.Equals(this.lastValue, value)) { - observer.OnNext(Tuple.Create(this.lastValue, value)); - } + foreach (IObserver> observer in this.observers) + { + observer.OnNext(Tuple.Create(this.lastValue, value)); + } - this.lastValue = value; + this.lastValue = value; + } + } + else + { + dirty = true; } } diff --git a/Perspex/Styling/IStyleable.cs b/Perspex/Styling/IStyleable.cs index eacf1f77af..988caa9f6c 100644 --- a/Perspex/Styling/IStyleable.cs +++ b/Perspex/Styling/IStyleable.cs @@ -17,7 +17,7 @@ namespace Perspex.Styling /// /// Gets the list of classes for the control. /// - PerspexList Classes { get; } + Classes Classes { get; } /// /// Binds a to a style.