@ -3,10 +3,10 @@
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Reactive ;
using System.Reactive.Disposables ;
using System.Reactive.Subjects ;
using Perspex.VisualTree ;
using Perspex.Threading ;
using Serilog ;
using Serilog.Core.Enrichers ;
@ -15,53 +15,15 @@ namespace Perspex.Layout
/// <summary>
/// Manages measuring and arranging of controls.
/// </summary>
/// <remarks>
/// Each layout root element such as a window has its own LayoutManager that is responsible
/// for laying out its child controls. When a layout is required the <see cref="LayoutNeeded"/>
/// observable will fire and the root element should respond by calling
/// <see cref="ExecuteLayoutPass"/> at the earliest opportunity to carry out the layout.
/// </remarks>
public class LayoutManager : ILayoutManager
{
/// <summary>
/// The maximum number of times a measure/arrange loop can be retried.
/// </summary>
private const int MaxTries = 3 ;
/// <summary>
/// Called when a layout is needed.
/// </summary>
private readonly Subject < Unit > _l ayoutNeeded ;
/// <summary>
/// Called when a layout is completed.
/// </summary>
private readonly Subject < Unit > _l ayoutCompleted ;
/// <summary>
/// Whether a measure is needed on the next layout pass.
/// </summary>
private bool _ measureNeeded = true ;
/// <summary>
/// The controls that need to be measured.
/// </summary>
private List < Item > _ toMeasure = new List < Item > ( ) ;
/// <summary>
/// The controls that need to be arranged.
/// </summary>
private List < Item > _ toArrange = new List < Item > ( ) ;
/// <summary>
/// Prevents re-entrancy.
/// </summary>
private bool _ running ;
/// <summary>
/// The logger to use.
/// </summary>
private readonly Queue < ILayoutable > _ toMeasure = new Queue < ILayoutable > ( ) ;
private readonly Queue < ILayoutable > _ toArrange = new Queue < ILayoutable > ( ) ;
private readonly Subject < Unit > _l ayoutNeeded = new Subject < Unit > ( ) ;
private readonly Subject < Unit > _l ayoutCompleted = new Subject < Unit > ( ) ;
private readonly ILogger _l og ;
private bool _f irst = true ;
private bool _ running ;
/// <summary>
/// Initializes a new instance of the <see cref="LayoutManager"/> class.
@ -74,9 +36,6 @@ namespace Perspex.Layout
new PropertyEnricher ( "SourceContext" , GetType ( ) ) ,
new PropertyEnricher ( "Id" , GetHashCode ( ) ) ,
} ) ;
_l ayoutNeeded = new Subject < Unit > ( ) ;
_l ayoutCompleted = new Subject < Unit > ( ) ;
}
/// <summary>
@ -114,20 +73,52 @@ namespace Perspex.Layout
private set ;
}
/// <summary>
/// Notifies the layout manager that a control requires a measure.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="distance">The control's distance from the layout root.</param>
public void InvalidateMeasure ( ILayoutable control , int distance )
{
Contract . Requires < ArgumentNullException > ( control ! = null ) ;
Dispatcher . UIThread . VerifyAccess ( ) ;
_ toMeasure . Enqueue ( control ) ;
_ toArrange . Enqueue ( control ) ;
FireLayoutNeeded ( ) ;
}
/// <summary>
/// Notifies the layout manager that a control requires an arrange.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="distance">The control's distance from the layout root.</param>
public void InvalidateArrange ( ILayoutable control , int distance )
{
Contract . Requires < ArgumentNullException > ( control ! = null ) ;
Dispatcher . UIThread . VerifyAccess ( ) ;
_ toArrange . Enqueue ( control ) ;
FireLayoutNeeded ( ) ;
}
/// <summary>
/// Executes a layout pass.
/// </summary>
public void ExecuteLayoutPass ( )
{
if ( _ running )
const int MaxPasses = 3 ;
Dispatcher . UIThread . VerifyAccess ( ) ;
if ( Root = = null )
{
return ;
th row n ew InvalidOpera tionException ( "Root m ust be set befo re executi ng layout pass." ) ;
}
using ( Disposable . Create ( ( ) = > _ running = false ) )
if ( ! _ running )
{
_ running = true ;
LayoutQueued = false ;
_l og . Information (
"Started layout pass. To measure: {Measure} To arrange: {Arrange}" ,
@ -137,21 +128,31 @@ namespace Perspex.Layout
var stopwatch = new System . Diagnostics . Stopwatch ( ) ;
stopwatch . Start ( ) ;
for ( int i = 0 ; i < MaxTries ; + + i )
try
{
if ( _ measureNeeded )
if ( _f irst )
{
ExecuteMeasure ( ) ;
_ measureNeeded = false ;
Measure ( Root ) ;
Arrange ( Root ) ;
_f irst = false ;
}
ExecuteArrange ( ) ;
if ( _ toMeasure . Count = = 0 )
for ( var pass = 0 ; pass < MaxPasses ; + + pass )
{
break ;
ExecuteMeasurePass ( ) ;
ExecuteArrangePass ( ) ;
if ( _ toMeasure . Count = = 0 )
{
break ;
}
}
}
finally
{
_ running = false ;
LayoutQueued = false ;
}
stopwatch . Stop ( ) ;
_l og . Information ( "Layout pass finised in {Time}" , stopwatch . Elapsed ) ;
@ -160,181 +161,58 @@ namespace Perspex.Layout
}
}
/// <summary>
/// Notifies the layout manager that a control requires a measure.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="distance">The control's distance from the layout root.</param>
public void InvalidateMeasure ( ILayoutable control , int distance )
private void ExecuteMeasurePass ( )
{
var item = new Item ( control , distance ) ;
_ toMeasure . Add ( item ) ;
_ toArrange . Add ( item ) ;
_ measureNeeded = true ;
if ( ! LayoutQueued )
while ( _ toMeasure . Count > 0 )
{
IVisual visual = control as IVisual ;
_l ayoutNeeded . OnNext ( Unit . Default ) ;
LayoutQueued = true ;
var next = _ toMeasure . Dequeue ( ) ;
Measure ( next ) ;
}
}
/// <summary>
/// Notifies the layout manager that a control requires an arrange.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="distance">The control's distance from the layout root.</param>
public void InvalidateArrange ( ILayoutable control , int distance )
private void ExecuteArrangePass ( )
{
_ toArrange . Add ( new Item ( control , distance ) ) ;
if ( ! LayoutQueued )
while ( _ toArrange . Count > 0 & & _ toMeasure . Count = = 0 )
{
IVisual visual = control as IVisual ;
_l ayoutNeeded . OnNext ( Unit . Default ) ;
LayoutQueued = true ;
var next = _ toArrange . Dequeue ( ) ;
Arrange ( next ) ;
}
}
/// <summary>
/// Executes the measure part of the layout pass.
/// </summary>
private void ExecuteMeasure ( )
private void Measure ( ILayoutable control )
{
for ( int i = 0 ; i < MaxTries ; + + i )
{
var measure = _ toMeasure ;
_ toMeasure = new List < Item > ( ) ;
measure . Sort ( ) ;
var root = control as ILayoutRoot ;
if ( ! Root . IsMeasureValid )
{
var size = new Size (
double . IsNaN ( Root . Width ) ? double . PositiveInfinity : Root . Width ,
double . IsNaN ( Root . Height ) ? double . PositiveInfinity : Root . Height ) ;
Root . Measure ( size ) ;
}
foreach ( var item in measure )
{
if ( ! item . Control . IsMeasureValid )
{
if ( item . Control ! = Root )
{
var parent = item . Control . GetVisualParent < ILayoutable > ( ) ;
while ( parent ! = null & & parent . PreviousMeasure = = null )
{
parent = parent . GetVisualParent < ILayoutable > ( ) ;
}
if ( parent ! = null & & parent . GetVisualRoot ( ) = = Root )
{
parent . Measure ( parent . PreviousMeasure . Value , true ) ;
}
}
}
}
if ( _ toMeasure . Count = = 0 )
{
break ;
}
if ( root ! = null )
{
root . Measure ( Size . Infinity ) ;
}
}
/// <summary>
/// Executes the arrange part of the layout pass.
/// </summary>
private void ExecuteArrange ( )
{
for ( int i = 0 ; i < MaxTries ; + + i )
else if ( control . PreviousMeasure . HasValue )
{
var arrange = _ toArrange ;
_ toArrange = new List < Item > ( ) ;
arrange . Sort ( ) ;
if ( ! Root . IsArrangeValid & & Root . IsMeasureValid )
{
Root . Arrange ( new Rect ( Root . DesiredSize ) ) ;
}
if ( _ toMeasure . Count > 0 )
{
return ;
}
foreach ( var item in arrange )
{
if ( ! item . Control . IsArrangeValid )
{
if ( item . Control ! = Root )
{
var control = item . Control ;
while ( control ! = null & & control . PreviousArrange = = null )
{
control = control . GetVisualParent < ILayoutable > ( ) ;
}
if ( control ! = null & & control . GetVisualRoot ( ) = = Root )
{
control . Arrange ( control . PreviousArrange . Value , true ) ;
}
if ( _ toMeasure . Count > 0 )
{
return ;
}
}
}
}
if ( _ toArrange . Count = = 0 )
{
break ;
}
control . Measure ( control . PreviousMeasure . Value ) ;
}
}
/// <summary>
/// An item to be layed-out.
/// </summary>
private class Item : IComparable < Item >
private void Arrange ( ILayoutable control )
{
/// <summary>
/// Initializes a new instance of the <see cref="Item"/> class.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="distance">The control's distance from the layout root.</param>
public Item ( ILayoutable control , int distance )
var root = control as ILayoutRoot ;
if ( root ! = null )
{
Control = control ;
Distance = distance ;
root . Arrange ( new Rect ( root . DesiredSize ) ) ;
}
else if ( control . PreviousArrange . HasValue )
{
control . Arrange ( control . PreviousArrange . Value ) ;
}
}
/// <summary>
/// Gets the control.
/// </summary>
public ILayoutable Control { get ; }
/// <summary>
/// Gets the control's distance from the layout root.
/// </summary>
public int Distance { get ; }
/// <summary>
/// Compares the distance of two items.
/// </summary>
/// <param name="other">The other item/</param>
/// <returns>The comparison.</returns>
public int CompareTo ( Item other )
private void FireLayoutNeeded ( )
{
if ( ! LayoutQueued )
{
return Distance - other . Distance ;
_l ayoutNeeded . OnNext ( Unit . Default ) ;
LayoutQueued = true ;
}
}
}