diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 2450f1a3a1..0499907ab8 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -210,7 +210,11 @@ namespace Avalonia /// The value. public object GetValue(AvaloniaProperty property) { - Contract.Requires(property != null); + if (property is null) + { + throw new ArgumentNullException(nameof(property)); + } + VerifyAccess(); if (property.IsDirect) @@ -231,7 +235,10 @@ namespace Avalonia /// The value. public T GetValue(AvaloniaProperty property) { - Contract.Requires(property != null); + if (property is null) + { + throw new ArgumentNullException(nameof(property)); + } return (T)GetValue((AvaloniaProperty)property); } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 56ad241187..ac7d2c60af 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -28,6 +28,8 @@ namespace Avalonia private readonly Dictionary _metadata; private readonly Dictionary _metadataCache = new Dictionary(); + private bool _hasMetadataOverrides; + /// /// Initializes a new instance of the class. /// @@ -92,6 +94,9 @@ namespace Avalonia Id = source.Id; _defaultMetadata = source._defaultMetadata; + // Properties that have different owner can't use fast path for metadata. + _hasMetadataOverrides = true; + if (metadata != null) { _metadata.Add(ownerType, metadata); @@ -446,31 +451,12 @@ namespace Avalonia /// public PropertyMetadata GetMetadata(Type type) { - Contract.Requires(type != null); - - PropertyMetadata result; - Type currentType = type; - - if (_metadataCache.TryGetValue(type, out result)) + if (!_hasMetadataOverrides) { - return result; + return _defaultMetadata; } - while (currentType != null) - { - if (_metadata.TryGetValue(currentType, out result)) - { - _metadataCache[type] = result; - - return result; - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - _metadataCache[type] = _defaultMetadata; - - return _defaultMetadata; + return GetMetadataWithOverrides(type); } /// @@ -535,6 +521,39 @@ namespace Avalonia metadata.Merge(baseMetadata, this); _metadata.Add(type, metadata); _metadataCache.Clear(); + + _hasMetadataOverrides = true; + } + + private PropertyMetadata GetMetadataWithOverrides(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (_metadataCache.TryGetValue(type, out PropertyMetadata result)) + { + return result; + } + + Type currentType = type; + + while (currentType != null) + { + if (_metadata.TryGetValue(currentType, out result)) + { + _metadataCache[type] = result; + + return result; + } + + currentType = currentType.GetTypeInfo().BaseType; + } + + _metadataCache[type] = _defaultMetadata; + + return _defaultMetadata; } [DebuggerHidden] diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs index d77cc269f9..cfb4b14b1c 100644 --- a/src/Avalonia.Layout/LayoutHelper.cs +++ b/src/Avalonia.Layout/LayoutHelper.cs @@ -21,8 +21,11 @@ namespace Avalonia.Layout /// The control's size. public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints) { - double width = (control.Width > 0) ? control.Width : constraints.Width; - double height = (control.Height > 0) ? control.Height : constraints.Height; + var controlWidth = control.Width; + var controlHeight = control.Height; + + double width = (controlWidth > 0) ? controlWidth : constraints.Width; + double height = (controlHeight > 0) ? controlHeight : constraints.Height; width = Math.Min(width, control.MaxWidth); width = Math.Max(width, control.MinWidth); height = Math.Min(height, control.MaxHeight); diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 855f123748..1792655a13 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -15,9 +15,15 @@ namespace Avalonia.Layout { private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); + private readonly Action _executeLayoutPass; private bool _queued; private bool _running; + public LayoutManager() + { + _executeLayoutPass = ExecuteLayoutPass; + } + /// public void InvalidateMeasure(ILayoutable control) { @@ -215,7 +221,7 @@ namespace Avalonia.Layout { if (!_queued && !_running) { - Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout); + Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout); _queued = true; } } diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs index eb0e4bd9f3..96f893e7b0 100644 --- a/src/Avalonia.Layout/LayoutQueue.cs +++ b/src/Avalonia.Layout/LayoutQueue.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; namespace Avalonia.Layout { @@ -18,9 +17,11 @@ namespace Avalonia.Layout _shouldEnqueue = shouldEnqueue; } - private Func _shouldEnqueue; - private Queue _inner = new Queue(); - private Dictionary _loopQueueInfo = new Dictionary(); + private readonly Func _shouldEnqueue; + private readonly Queue _inner = new Queue(); + private readonly Dictionary _loopQueueInfo = new Dictionary(); + private readonly List> _notFinalizedBuffer = new List>(); + private int _maxEnqueueCountPerLoop = 1; public int Count => _inner.Count; @@ -60,13 +61,19 @@ namespace Avalonia.Layout public void EndLoop() { - var notfinalized = _loopQueueInfo.Where(v => v.Value.Count >= _maxEnqueueCountPerLoop).ToArray(); + foreach (KeyValuePair info in _loopQueueInfo) + { + if (info.Value.Count >= _maxEnqueueCountPerLoop) + { + _notFinalizedBuffer.Add(info); + } + } _loopQueueInfo.Clear(); - //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle - //one more time as a final attempt - foreach (var item in notfinalized) + // Prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle + // one more time as a final attempt. + foreach (var item in _notFinalizedBuffer) { if (_shouldEnqueue(item.Key)) { @@ -74,6 +81,8 @@ namespace Avalonia.Layout _inner.Enqueue(item.Key); } } + + _notFinalizedBuffer.Clear(); } } } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index b0757a823d..4732808b91 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -518,17 +518,25 @@ namespace Avalonia.Layout var width = measured.Width; var height = measured.Height; - if (!double.IsNaN(Width)) { - width = Width; + double widthCache = Width; + + if (!double.IsNaN(widthCache)) + { + width = widthCache; + } } width = Math.Min(width, MaxWidth); width = Math.Max(width, MinWidth); - if (!double.IsNaN(Height)) { - height = Height; + double heightCache = Height; + + if (!double.IsNaN(heightCache)) + { + height = heightCache; + } } height = Math.Min(height, MaxHeight); @@ -562,11 +570,19 @@ namespace Avalonia.Layout double width = 0; double height = 0; - foreach (ILayoutable child in this.GetVisualChildren()) + var visualChildren = VisualChildren; + var visualCount = visualChildren.Count; + + for (var i = 0; i < visualCount; i++) { - child.Measure(availableSize); - width = Math.Max(width, child.DesiredSize.Width); - height = Math.Max(height, child.DesiredSize.Height); + IVisual visual = visualChildren[i]; + + if (visual is ILayoutable layoutable) + { + layoutable.Measure(availableSize); + width = Math.Max(width, layoutable.DesiredSize.Width); + height = Math.Max(height, layoutable.DesiredSize.Height); + } } return new Size(width, height); @@ -594,6 +610,7 @@ namespace Avalonia.Layout var verticalAlignment = VerticalAlignment; var size = availableSizeMinusMargins; var scale = GetLayoutScale(); + var useLayoutRounding = UseLayoutRounding; if (horizontalAlignment != HorizontalAlignment.Stretch) { @@ -607,7 +624,7 @@ namespace Avalonia.Layout size = LayoutHelper.ApplyLayoutConstraints(this, size); - if (UseLayoutRounding) + if (useLayoutRounding) { size = new Size( Math.Ceiling(size.Width * scale) / scale, @@ -641,7 +658,7 @@ namespace Avalonia.Layout break; } - if (UseLayoutRounding) + if (useLayoutRounding) { originX = Math.Floor(originX * scale) / scale; originY = Math.Floor(originY * scale) / scale; @@ -658,9 +675,19 @@ namespace Avalonia.Layout /// The actual size used. protected virtual Size ArrangeOverride(Size finalSize) { - foreach (ILayoutable child in this.GetVisualChildren().OfType()) + var arrangeRect = new Rect(finalSize); + + var visualChildren = VisualChildren; + var visualCount = visualChildren.Count; + + for (var i = 0; i < visualCount; i++) { - child.Arrange(new Rect(finalSize)); + IVisual visual = visualChildren[i]; + + if (visual is ILayoutable layoutable) + { + layoutable.Arrange(arrangeRect); + } } return finalSize;