From deba950eb24ba0ad947ba1723e3f62a217a10d32 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 May 2015 21:00:38 +0200 Subject: [PATCH 1/5] Make sure a render takes place after layout. --- Perspex.Controls/TopLevel.cs | 9 ++++++++ Perspex.Layout/ILayoutManager.cs | 5 ++++ Perspex.Layout/LayoutManager.cs | 18 +++++++++++---- TestApplication/Program.cs | 16 ++++++------- .../TopLevelTests.cs | 23 +++++++++++++++++++ 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/Perspex.Controls/TopLevel.cs b/Perspex.Controls/TopLevel.cs index 32d0021ca8..8612155b71 100644 --- a/Perspex.Controls/TopLevel.cs +++ b/Perspex.Controls/TopLevel.cs @@ -128,6 +128,7 @@ namespace Perspex.Controls this.LayoutManager.Root = this; this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded()); + this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted()); this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded()); IStyler styler = Locator.Current.GetService(); @@ -296,6 +297,14 @@ namespace Perspex.Controls this.dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } + /// + /// Handles a layout completion request from . + /// + private void HandleLayoutCompleted() + { + this.renderManager.InvalidateRender(this); + } + /// /// Handles a render request from . /// diff --git a/Perspex.Layout/ILayoutManager.cs b/Perspex.Layout/ILayoutManager.cs index ac42ff713a..4572926a18 100644 --- a/Perspex.Layout/ILayoutManager.cs +++ b/Perspex.Layout/ILayoutManager.cs @@ -33,6 +33,11 @@ namespace Perspex.Layout /// IObservable LayoutNeeded { get; } + /// + /// Gets an observable that is fired when a layout pass is completed. + /// + IObservable LayoutCompleted { get; } + /// /// Gets a value indicating whether a layout is queued. /// diff --git a/Perspex.Layout/LayoutManager.cs b/Perspex.Layout/LayoutManager.cs index 99674b7c66..2a5975da35 100644 --- a/Perspex.Layout/LayoutManager.cs +++ b/Perspex.Layout/LayoutManager.cs @@ -33,6 +33,11 @@ namespace Perspex.Layout /// private Subject layoutNeeded; + /// + /// Called when a layout is completed. + /// + private Subject layoutCompleted; + /// /// Whether a measure is needed on the next layout pass. /// @@ -54,6 +59,7 @@ namespace Perspex.Layout public LayoutManager() { this.layoutNeeded = new Subject(); + this.layoutCompleted = new Subject(); } /// @@ -71,10 +77,12 @@ namespace Perspex.Layout /// /// Gets an observable that is fired when a layout pass is needed. /// - public IObservable LayoutNeeded - { - get { return this.layoutNeeded; } - } + public IObservable LayoutNeeded => this.layoutNeeded; + + /// + /// Gets an observable that is fired when a layout pass is completed. + /// + public IObservable LayoutCompleted => this.layoutCompleted; /// /// Gets a value indicating whether a layout is queued. @@ -111,6 +119,8 @@ namespace Perspex.Layout break; } } + + this.layoutCompleted.OnNext(Unit.Default); } /// diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 8b26fdc430..f20334c20f 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -178,14 +178,14 @@ namespace TestApplication DevTools.Attach(window); - var renderer = ((IRenderRoot)window).Renderer; - var last = renderer.RenderCount; - DispatcherTimer.Run(() => - { - fps.Text = "FPS: " + (renderer.RenderCount - last); - last = renderer.RenderCount; - return true; - }, TimeSpan.FromSeconds(1)); + //var renderer = ((IRenderRoot)window).Renderer; + //var last = renderer.RenderCount; + //DispatcherTimer.Run(() => + //{ + // fps.Text = "FPS: " + (renderer.RenderCount - last); + // last = renderer.RenderCount; + // return true; + //}, TimeSpan.FromSeconds(1)); window.Show(); Application.Current.Run(window); diff --git a/Tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/Tests/Perspex.Controls.UnitTests/TopLevelTests.cs index a360f415a0..f0301f9a31 100644 --- a/Tests/Perspex.Controls.UnitTests/TopLevelTests.cs +++ b/Tests/Perspex.Controls.UnitTests/TopLevelTests.cs @@ -17,6 +17,8 @@ namespace Perspex.Controls.UnitTests using Ploeh.AutoFixture; using Ploeh.AutoFixture.AutoMoq; using Splat; + using System.Reactive; + using System.Reactive.Subjects; using Xunit; public class TopLevelTests @@ -163,6 +165,27 @@ namespace Perspex.Controls.UnitTests } } + [Fact] + public void Render_Should_Be_Scheduled_After_Layout_Pass() + { + using (Locator.CurrentMutable.WithResolver()) + { + this.RegisterServices(); + var completed = new Subject(); + var layoutManagerMock = Mock.Get(Locator.Current.GetService()); + layoutManagerMock.Setup(x => x.LayoutCompleted).Returns(completed); + + var impl = new Mock(); + impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); + + var target = new TestTopLevel(impl.Object); + completed.OnNext(Unit.Default); + + var renderManagerMock = Mock.Get(Locator.Current.GetService()); + renderManagerMock.Verify(x => x.InvalidateRender(target)); + } + } + [Fact] public void Width_And_Height_Should_Be_Set_After_Window_Resize_Notification() { From 6de240cc4d19871b86e0caba0afc8ffcf6d7c27c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 May 2015 21:55:56 +0200 Subject: [PATCH 2/5] Stop DevTools crashing when tree changed. PerspexReadOnlyListView had some problems... --- Perspex.Base/Collections/PerspexReadOnlyListView.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs index 0f9d1cd0c4..7c6b3ee4dd 100644 --- a/Perspex.Base/Collections/PerspexReadOnlyListView.cs +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -115,14 +115,14 @@ namespace Perspex.Collections case NotifyCollectionChangedAction.Add: ev = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, - e.NewItems, + e.NewItems.Cast().ToList(), e.NewStartingIndex); break; case NotifyCollectionChangedAction.Remove: ev = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, e.OldItems, - e.NewStartingIndex); + e.OldStartingIndex); break; case NotifyCollectionChangedAction.Replace: ev = new NotifyCollectionChangedEventArgs( @@ -248,7 +248,7 @@ namespace Perspex.Collections return this.GetEnumerator(); } - private IList ConvertList(IEnumerable list) + private IList ConvertList(IEnumerable list) { return list.Cast().Select(this.convert).ToList(); } @@ -271,7 +271,7 @@ namespace Perspex.Collections ev = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, this.ConvertList(e.OldItems), - e.NewStartingIndex); + e.OldStartingIndex); break; case NotifyCollectionChangedAction.Replace: ev = new NotifyCollectionChangedEventArgs( From 98c2357aaba13c016d4418ecdfc5105c95e3596a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 May 2015 21:57:47 +0200 Subject: [PATCH 3/5] Commited that by accident. --- Perspex.Base/Collections/PerspexReadOnlyListView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs index 7c6b3ee4dd..b12e52efbb 100644 --- a/Perspex.Base/Collections/PerspexReadOnlyListView.cs +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -115,7 +115,7 @@ namespace Perspex.Collections case NotifyCollectionChangedAction.Add: ev = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, - e.NewItems.Cast().ToList(), + e.NewItems, e.NewStartingIndex); break; case NotifyCollectionChangedAction.Remove: From 0359f8c9a371c1377c8f9628fe139840bce9b93f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 May 2015 00:15:31 +0200 Subject: [PATCH 4/5] Removed PerspexReadOnlyListView I can't see why it was needed, just assigning the control's logical child to the presenter's should work? No? Doesn't seem like it breaks anything.... --- .../Collections/PerspexReadOnlyListView.cs | 128 ------------------ Perspex.Controls/ContentControl.cs | 8 +- Perspex.Controls/DropDown.cs | 4 +- Perspex.Controls/TabControl.cs | 5 +- 4 files changed, 6 insertions(+), 139 deletions(-) diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs index b12e52efbb..918fd6e885 100644 --- a/Perspex.Base/Collections/PerspexReadOnlyListView.cs +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -13,134 +13,6 @@ namespace Perspex.Collections using System.ComponentModel; using System.Linq; - public class PerspexReadOnlyListView : IPerspexReadOnlyList, IDisposable - { - private IPerspexReadOnlyList source; - - public PerspexReadOnlyListView() - : this(null) - { - } - - public PerspexReadOnlyListView(IPerspexReadOnlyList source) - { - this.source = source; - - if (source != null) - { - this.source.CollectionChanged += this.SourceCollectionChanged; - } - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - public event PropertyChangedEventHandler PropertyChanged; - - public int Count - { - get { return this.source?.Count ?? 0; } - } - - public IPerspexReadOnlyList Source - { - get - { - return this.source; - } - - set - { - if (this.source != null) - { - this.source.CollectionChanged -= this.SourceCollectionChanged; - - if (this.CollectionChanged != null) - { - var ev = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - this.source, - 0); - this.CollectionChanged(this, ev); - } - } - - this.source = value; - - if (this.source != null) - { - this.source.CollectionChanged += this.SourceCollectionChanged; - - if (this.CollectionChanged != null) - { - var ev = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - this.source.ToList(), - 0); - this.CollectionChanged(this, ev); - } - } - } - } - - public T this[int index] - { - get { return this.source[index]; } - } - - public void Dispose() - { - this.source.CollectionChanged -= this.SourceCollectionChanged; - } - - public IEnumerator GetEnumerator() - { - return (this.source != null) ? - this.source.GetEnumerator() : - Enumerable.Empty().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (this.CollectionChanged != null) - { - NotifyCollectionChangedEventArgs ev; - - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - ev = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - e.NewItems, - e.NewStartingIndex); - break; - case NotifyCollectionChangedAction.Remove: - ev = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - e.OldItems, - e.OldStartingIndex); - break; - case NotifyCollectionChangedAction.Replace: - ev = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Replace, - e.NewItems, - e.OldItems, - e.OldStartingIndex); - break; - - default: - throw new NotSupportedException("Action not yet implemented."); - } - - this.CollectionChanged(this, ev); - } - } - } - public class PerspexReadOnlyListView : IPerspexReadOnlyList, IDisposable { private IPerspexReadOnlyList source; diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index aa0bfd803f..84c327a36b 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -24,7 +24,7 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalContentAlignmentProperty = PerspexProperty.Register("VerticalContentAlignment"); - private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); + private IPerspexReadOnlyList logicalChildren = new PerspexList(); public ContentControl() { @@ -63,11 +63,7 @@ namespace Perspex.Controls if (presenter != null) { - this.logicalChildren.Source = ((ILogical)presenter).LogicalChildren; - } - else - { - this.logicalChildren.Source = null; + this.logicalChildren = ((ILogical)presenter)?.LogicalChildren; } } diff --git a/Perspex.Controls/DropDown.cs b/Perspex.Controls/DropDown.cs index ee1a03f1a3..f20409ac8a 100644 --- a/Perspex.Controls/DropDown.cs +++ b/Perspex.Controls/DropDown.cs @@ -28,7 +28,7 @@ namespace Perspex.Controls public static readonly PerspexProperty IsDropDownOpenProperty = PerspexProperty.Register("IsDropDownOpen"); - private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); + private IPerspexReadOnlyList logicalChildren = new PerspexSingleItemList(); public DropDown() { @@ -77,7 +77,7 @@ namespace Perspex.Controls { var container = this.GetTemplateChild("container"); ((IItemsPanel)container).ChildLogicalParent = this; - this.logicalChildren.Source = ((ILogical)container).LogicalChildren; + this.logicalChildren = ((ILogical)container).LogicalChildren; } private void SetContentParent(Tuple change) diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index e0e7b1bd4d..4f6654f966 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -25,8 +25,7 @@ namespace Perspex.Controls public static readonly PerspexProperty TransitionProperty = Deck.TransitionProperty.AddOwner(); - private PerspexReadOnlyListView logicalChildren = - new PerspexReadOnlyListView(); + private IPerspexReadOnlyList logicalChildren = new PerspexSingleItemList(); static TabControl() { @@ -81,7 +80,7 @@ namespace Perspex.Controls base.OnTemplateApplied(); this.deck = this.GetTemplateChild("deck"); - this.logicalChildren.Source = ((ILogical)deck).LogicalChildren; + this.logicalChildren = ((ILogical)deck).LogicalChildren; } } } From 3aa621cd963543689cf41b681be12c7c16d47563 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 May 2015 00:31:58 +0200 Subject: [PATCH 5/5] Revert "Removed PerspexReadOnlyListView" This reverts commit 0359f8c9a371c1377c8f9628fe139840bce9b93f. --- .../Collections/PerspexReadOnlyListView.cs | 128 ++++++++++++++++++ Perspex.Controls/ContentControl.cs | 8 +- Perspex.Controls/DropDown.cs | 4 +- Perspex.Controls/TabControl.cs | 5 +- 4 files changed, 139 insertions(+), 6 deletions(-) diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs index 918fd6e885..b12e52efbb 100644 --- a/Perspex.Base/Collections/PerspexReadOnlyListView.cs +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -13,6 +13,134 @@ namespace Perspex.Collections using System.ComponentModel; using System.Linq; + public class PerspexReadOnlyListView : IPerspexReadOnlyList, IDisposable + { + private IPerspexReadOnlyList source; + + public PerspexReadOnlyListView() + : this(null) + { + } + + public PerspexReadOnlyListView(IPerspexReadOnlyList source) + { + this.source = source; + + if (source != null) + { + this.source.CollectionChanged += this.SourceCollectionChanged; + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public int Count + { + get { return this.source?.Count ?? 0; } + } + + public IPerspexReadOnlyList Source + { + get + { + return this.source; + } + + set + { + if (this.source != null) + { + this.source.CollectionChanged -= this.SourceCollectionChanged; + + if (this.CollectionChanged != null) + { + var ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + this.source, + 0); + this.CollectionChanged(this, ev); + } + } + + this.source = value; + + if (this.source != null) + { + this.source.CollectionChanged += this.SourceCollectionChanged; + + if (this.CollectionChanged != null) + { + var ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + this.source.ToList(), + 0); + this.CollectionChanged(this, ev); + } + } + } + } + + public T this[int index] + { + get { return this.source[index]; } + } + + public void Dispose() + { + this.source.CollectionChanged -= this.SourceCollectionChanged; + } + + public IEnumerator GetEnumerator() + { + return (this.source != null) ? + this.source.GetEnumerator() : + Enumerable.Empty().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (this.CollectionChanged != null) + { + NotifyCollectionChangedEventArgs ev; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + e.NewItems, + e.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Remove: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + e.OldItems, + e.OldStartingIndex); + break; + case NotifyCollectionChangedAction.Replace: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + e.NewItems, + e.OldItems, + e.OldStartingIndex); + break; + + default: + throw new NotSupportedException("Action not yet implemented."); + } + + this.CollectionChanged(this, ev); + } + } + } + public class PerspexReadOnlyListView : IPerspexReadOnlyList, IDisposable { private IPerspexReadOnlyList source; diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index 84c327a36b..aa0bfd803f 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -24,7 +24,7 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalContentAlignmentProperty = PerspexProperty.Register("VerticalContentAlignment"); - private IPerspexReadOnlyList logicalChildren = new PerspexList(); + private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); public ContentControl() { @@ -63,7 +63,11 @@ namespace Perspex.Controls if (presenter != null) { - this.logicalChildren = ((ILogical)presenter)?.LogicalChildren; + this.logicalChildren.Source = ((ILogical)presenter).LogicalChildren; + } + else + { + this.logicalChildren.Source = null; } } diff --git a/Perspex.Controls/DropDown.cs b/Perspex.Controls/DropDown.cs index f20409ac8a..ee1a03f1a3 100644 --- a/Perspex.Controls/DropDown.cs +++ b/Perspex.Controls/DropDown.cs @@ -28,7 +28,7 @@ namespace Perspex.Controls public static readonly PerspexProperty IsDropDownOpenProperty = PerspexProperty.Register("IsDropDownOpen"); - private IPerspexReadOnlyList logicalChildren = new PerspexSingleItemList(); + private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); public DropDown() { @@ -77,7 +77,7 @@ namespace Perspex.Controls { var container = this.GetTemplateChild("container"); ((IItemsPanel)container).ChildLogicalParent = this; - this.logicalChildren = ((ILogical)container).LogicalChildren; + this.logicalChildren.Source = ((ILogical)container).LogicalChildren; } private void SetContentParent(Tuple change) diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index 4f6654f966..e0e7b1bd4d 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -25,7 +25,8 @@ namespace Perspex.Controls public static readonly PerspexProperty TransitionProperty = Deck.TransitionProperty.AddOwner(); - private IPerspexReadOnlyList logicalChildren = new PerspexSingleItemList(); + private PerspexReadOnlyListView logicalChildren = + new PerspexReadOnlyListView(); static TabControl() { @@ -80,7 +81,7 @@ namespace Perspex.Controls base.OnTemplateApplied(); this.deck = this.GetTemplateChild("deck"); - this.logicalChildren = ((ILogical)deck).LogicalChildren; + this.logicalChildren.Source = ((ILogical)deck).LogicalChildren; } } }