// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Layout { using System; using System.Reactive; using System.Reactive.Subjects; using NGenerics.DataStructures.General; /// /// Manages measuring and arranging of controls. /// /// /// 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 /// observable will fire and the root element should respond by calling /// at the earliest opportunity to carry out the layout. /// public class LayoutManager : ILayoutManager { /// /// The maximum number of times a measure/arrange loop can be retried. /// private const int MaxTries = 3; /// /// Called when a layout is needed. /// private Subject layoutNeeded; /// /// Whether a measure is needed on the next layout pass. /// private bool measureNeeded = true; /// /// The controls that need to be measured, sorted by distance to layout root. /// private Heap toMeasure = new Heap(HeapType.Minimum); /// /// The controls that need to be arranged, sorted by distance to layout root. /// private Heap toArrange = new Heap(HeapType.Minimum); /// /// Initializes a new instance of the class. /// public LayoutManager() { this.layoutNeeded = new Subject(); } /// /// Gets or sets the root element that the manager is attached to. /// /// /// This must be set before the layout manager can be used. /// public ILayoutRoot Root { get; set; } /// /// Gets an observable that is fired when a layout pass is needed. /// public IObservable LayoutNeeded { get { return this.layoutNeeded; } } /// /// Gets a value indicating whether a layout is queued. /// /// /// Returns true when has been fired, but /// has not yet been called. /// public bool LayoutQueued { get; private set; } /// /// Executes a layout pass. /// public void ExecuteLayoutPass() { this.LayoutQueued = false; for (int i = 0; i < MaxTries; ++i) { if (this.measureNeeded) { this.ExecuteMeasure(); this.measureNeeded = false; } this.ExecuteArrange(); if (this.toMeasure.Count == 0) { break; } } } /// /// Notifies the layout manager that a control requires a measure. /// /// The control. /// The control's distance from the layout root. public void InvalidateMeasure(ILayoutable control, int distance) { var item = new Item(control, distance); this.toMeasure.Add(item); this.toArrange.Add(item); this.measureNeeded = true; if (!this.LayoutQueued) { IVisual visual = control as IVisual; this.layoutNeeded.OnNext(Unit.Default); this.LayoutQueued = true; } } /// /// Notifies the layout manager that a control requires an arrange. /// /// The control. /// The control's distance from the layout root. public void InvalidateArrange(ILayoutable control, int distance) { this.toArrange.Add(new Item(control, distance)); if (!this.LayoutQueued) { IVisual visual = control as IVisual; this.layoutNeeded.OnNext(Unit.Default); this.LayoutQueued = true; } } /// /// Executes the measure part of the layout pass. /// private void ExecuteMeasure() { for (int i = 0; i < MaxTries; ++i) { var measure = this.toMeasure; this.toMeasure = new Heap(HeapType.Minimum); if (!this.Root.IsMeasureValid) { this.Root.Measure(this.Root.ClientSize); } foreach (var item in measure) { if (!item.Control.IsMeasureValid) { if (item.Control != this.Root) { var parent = item.Control.GetVisualParent(); while (parent.PreviousMeasure == null) { parent = parent.GetVisualParent(); } parent.Measure(parent.PreviousMeasure.Value, true); } } } if (this.toMeasure.Count == 0) { break; } } } /// /// Executes the arrange part of the layout pass. /// private void ExecuteArrange() { for (int i = 0; i < MaxTries; ++i) { var arrange = this.toArrange; this.toArrange = new Heap(HeapType.Minimum); if (!this.Root.IsArrangeValid) { this.Root.Arrange(new Rect(this.Root.ClientSize)); } if (this.toMeasure.Count > 0) { return; } foreach (var item in arrange) { if (!item.Control.IsArrangeValid) { if (item.Control != this.Root) { var control = item.Control; while (control.PreviousArrange == null) { control = control.GetVisualParent(); } control.Arrange(control.PreviousArrange.Value, true); if (this.toMeasure.Count > 0) { return; } } } } if (this.toArrange.Count == 0) { break; } } } private class Item : IComparable { public Item(ILayoutable control, int distance) { this.Control = control; this.Distance = distance; } public ILayoutable Control { get; private set; } public int Distance { get; private set; } public int CompareTo(Item other) { return this.Distance - other.Distance; } } } }