Created
November 22, 2017 17:55
-
-
Save grokys/1ff2a816ec069a5986257882ce31f161 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) The Avalonia Project. All rights reserved. | |
// Licensed under the MIT license. See licence.md file in the project root for full license information. | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Avalonia.Logging; | |
using Avalonia.Threading; | |
namespace Avalonia.Layout | |
{ | |
/// <summary> | |
/// Manages measuring and arranging of controls. | |
/// </summary> | |
public class LayoutManager : ILayoutManager | |
{ | |
private Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>(); | |
private Queue<ILayoutable> _toArrange = new Queue<ILayoutable>(); | |
private bool _queued; | |
private bool _running; | |
/// <summary> | |
/// Gets the layout manager. | |
/// </summary> | |
public static ILayoutManager Instance => AvaloniaLocator.Current.GetService<ILayoutManager>(); | |
/// <inheritdoc/> | |
public void InvalidateMeasure(ILayoutable control) | |
{ | |
Contract.Requires<ArgumentNullException>(control != null); | |
Dispatcher.UIThread.VerifyAccess(); | |
if (!control.IsAttachedToVisualTree) | |
{ | |
#if DEBUG | |
throw new AvaloniaInternalException( | |
"LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree."); | |
#else | |
return; | |
#endif | |
} | |
_toMeasure.Enqueue(control); | |
_toArrange.Enqueue(control); | |
QueueLayoutPass(); | |
} | |
/// <inheritdoc/> | |
public void InvalidateArrange(ILayoutable control) | |
{ | |
Contract.Requires<ArgumentNullException>(control != null); | |
Dispatcher.UIThread.VerifyAccess(); | |
if (!control.IsAttachedToVisualTree) | |
{ | |
#if DEBUG | |
throw new AvaloniaInternalException( | |
"LayoutManager.InvalidateArrange called on a control that is detached from the visual tree."); | |
#else | |
return; | |
#endif | |
} | |
_toArrange.Enqueue(control); | |
QueueLayoutPass(); | |
} | |
/// <inheritdoc/> | |
public void ExecuteLayoutPass() | |
{ | |
const int MaxPasses = 3; | |
Dispatcher.UIThread.VerifyAccess(); | |
if (!_running) | |
{ | |
_running = true; | |
Logger.Information( | |
LogArea.Layout, | |
this, | |
"Started layout pass. To measure: {Measure} To arrange: {Arrange}", | |
_toMeasure.Count, | |
_toArrange.Count); | |
var stopwatch = new System.Diagnostics.Stopwatch(); | |
stopwatch.Start(); | |
try | |
{ | |
for (var pass = 0; pass < MaxPasses; ++pass) | |
{ | |
var toMeasure = _toMeasure; | |
var toArrange = _toArrange; | |
_toMeasure = new Queue<ILayoutable>(); | |
_toArrange = new Queue<ILayoutable>(); | |
ExecuteMeasurePass(toMeasure); | |
ExecuteArrangePass(toArrange); | |
if (_toMeasure.Count == 0) | |
{ | |
break; | |
} | |
} | |
} | |
finally | |
{ | |
_running = false; | |
} | |
stopwatch.Stop(); | |
Logger.Information(LogArea.Layout, this, "Layout pass finised in {Time}", stopwatch.Elapsed); | |
} | |
_queued = false; | |
} | |
/// <inheritdoc/> | |
public void ExecuteInitialLayoutPass(ILayoutRoot root) | |
{ | |
Measure(root); | |
Arrange(root); | |
// Running the initial layout pass may have caused some control to be invalidated | |
// so run a full layout pass now (this usually due to scrollbars; its not known | |
// whether they will need to be shown until the layout pass has run and if the | |
// first guess was incorrect the layout will need to be updated). | |
ExecuteLayoutPass(); | |
} | |
private void ExecuteMeasurePass(Queue<ILayoutable> toMeasure) | |
{ | |
while (toMeasure.Count > 0) | |
{ | |
var control = toMeasure.Dequeue(); | |
if (!control.IsMeasureValid && control.IsAttachedToVisualTree) | |
{ | |
Measure(control); | |
} | |
} | |
} | |
private void ExecuteArrangePass(Queue<ILayoutable> toArrange) | |
{ | |
while (toArrange.Count > 0) | |
{ | |
var control = toArrange.Dequeue(); | |
if (!control.IsArrangeValid && control.IsAttachedToVisualTree) | |
{ | |
Arrange(control); | |
} | |
} | |
} | |
private void Measure(ILayoutable control) | |
{ | |
// Controls closest to the visual root need to be arranged first. We don't try to store | |
// ordered invalidation lists, instead we traverse the tree upwards, measuring the | |
// controls closest to the root first. This has been shown by benchmarks to be the | |
// fastest and most memory-efficent algorithm. | |
if (control.VisualParent is ILayoutable parent) | |
{ | |
Measure(parent); | |
} | |
// If the control being measured has IsMeasureValid == true here then its measure was | |
// handed by an ancestor and can be ignored. The measure may have also caused the | |
// control to be removed. | |
if (!control.IsMeasureValid && control.IsAttachedToVisualTree) | |
{ | |
if (control is ILayoutRoot root) | |
{ | |
root.Measure(Size.Infinity); | |
} | |
else | |
{ | |
control.Measure(control.PreviousMeasure.Value); | |
} | |
} | |
} | |
private void Arrange(ILayoutable control) | |
{ | |
if (control.VisualParent is ILayoutable parent) | |
{ | |
Arrange(parent); | |
} | |
if (!control.IsArrangeValid && control.IsAttachedToVisualTree) | |
{ | |
if (control is IEmbeddedLayoutRoot embeddedRoot) | |
control.Arrange(new Rect(embeddedRoot.AllocatedSize)); | |
else if (control is ILayoutRoot root) | |
control.Arrange(new Rect(root.DesiredSize)); | |
else if (control.PreviousArrange != null) | |
{ | |
// Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else. | |
// Condition observed: control.VisualParent is Scrollbar, control is Border. | |
control.Arrange(control.PreviousArrange.Value); | |
} | |
} | |
} | |
private void QueueLayoutPass() | |
{ | |
if (!_queued && !_running) | |
{ | |
Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Layout); | |
_queued = true; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment