From 094462639e10fe743786438bffa847abaaa64a35 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 07:59:38 +0200 Subject: [PATCH 1/5] Add StackLayout.DisableVirtualization And a couple of updates to `ItemsRepeater`. Ported from https://github.com/microsoft/microsoft-ui-xaml/commit/f3862ecf86530a9341002fcab287ae1dcfccaf53 --- .../Repeater/ItemsRepeater.cs | 28 ++++++++++++++----- src/Avalonia.Layout/FlowLayoutAlgorithm.cs | 10 ++++--- src/Avalonia.Layout/StackLayout.cs | 16 +++++++++++ src/Avalonia.Layout/UniformGridLayout.cs | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 086599d0bb..12fba12c8c 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -379,14 +380,19 @@ namespace Avalonia.Controls { if (property == ItemsProperty) { + var oldEnumerable = oldValue.GetValueOrDefault(); var newEnumerable = newValue.GetValueOrDefault(); - var newDataSource = newEnumerable as ItemsSourceView; - if (newEnumerable != null && newDataSource == null) + + if (oldEnumerable != newEnumerable) { - newDataSource = new ItemsSourceView(newEnumerable); - } + var newDataSource = newEnumerable as ItemsSourceView; + if (newEnumerable != null && newDataSource == null) + { + newDataSource = new ItemsSourceView(newEnumerable); + } - OnDataSourcePropertyChanged(ItemsSourceView, newDataSource); + OnDataSourcePropertyChanged(ItemsSourceView, newDataSource); + } } else if (property == ItemTemplateProperty) { @@ -431,8 +437,16 @@ namespace Avalonia.Controls private int GetElementIndexImpl(IControl element) { - var virtInfo = TryGetVirtualizationInfo(element); - return _viewManager.GetElementIndex(virtInfo); + // Verify that element is actually a child of this ItemsRepeater + var parent = element.GetVisualParent(); + + if (parent == this) + { + var virtInfo = TryGetVirtualizationInfo(element); + return _viewManager.GetElementIndex(virtInfo); + } + + return -1; } private IControl GetElementFromIndexImpl(int index) diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs index 7f44c80a64..0d64f8dfd5 100644 --- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs +++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs @@ -74,6 +74,7 @@ namespace Avalonia.Layout double lineSpacing, int maxItemsPerLine, ScrollOrientation orientation, + bool disableVirtualization, string layoutId) { _orientation.ScrollOrientation = orientation; @@ -95,14 +96,14 @@ namespace Avalonia.Layout _elementManager.OnBeginMeasure(orientation); int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId); - Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId); - Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId); + Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId); + Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId); if (isWrapping && IsReflowRequired()) { var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0); _orientation.SetMinorStart(ref firstElementBounds, 0); _elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds); - Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId); + Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId); } RaiseLineArranged(); @@ -273,6 +274,7 @@ namespace Avalonia.Layout double minItemSpacing, double lineSpacing, int maxItemsPerLine, + bool disableVirtualization, string layoutId) { if (anchorIndex != -1) @@ -288,7 +290,7 @@ namespace Avalonia.Layout bool lineNeedsReposition = false; while (_elementManager.IsIndexValidInData(currentIndex) && - ShouldContinueFillingUpSpace(previousIndex, direction)) + (disableVirtualization || ShouldContinueFillingUpSpace(previousIndex, direction))) { // Ensure layout element. _elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId); diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index 9b8eb4814e..b383c205f9 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -14,6 +14,12 @@ namespace Avalonia.Layout /// public class StackLayout : VirtualizingLayout, IFlowLayoutAlgorithmDelegates { + /// + /// Defines the property. + /// + public static readonly StyledProperty DisableVirtualizationProperty = + AvaloniaProperty.Register(nameof(DisableVirtualization)); + /// /// Defines the property. /// @@ -36,6 +42,15 @@ namespace Avalonia.Layout LayoutId = "StackLayout"; } + /// + /// Gets or sets a value indicating whether virtualization is disabled on the layout. + /// + public bool DisableVirtualization + { + get => GetValue(DisableVirtualizationProperty); + set => SetValue(DisableVirtualizationProperty, value); + } + /// /// Gets or sets the axis along which items are laid out. /// @@ -270,6 +285,7 @@ namespace Avalonia.Layout Spacing, int.MaxValue, _orientation.ScrollOrientation, + DisableVirtualization, LayoutId); return new Size(desiredSize.Width, desiredSize.Height); diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs index ee9cff4a01..d565fae074 100644 --- a/src/Avalonia.Layout/UniformGridLayout.cs +++ b/src/Avalonia.Layout/UniformGridLayout.cs @@ -433,6 +433,7 @@ namespace Avalonia.Layout LineSpacing, _maximumRowsOrColumns, _orientation.ScrollOrientation, + false, LayoutId); // If after Measure the first item is in the realization rect, then we revoke grid state's ownership, From 6eb7f5b034aabb41edb2419cee09b524f5d0c0d2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 08:09:26 +0200 Subject: [PATCH 2/5] Fix issue with multiple resets causing a crash due to duplicate keys. Ported from https://github.com/microsoft/microsoft-ui-xaml/commit/464a4482772bd54f2b72cbaa8012704649120b32 --- src/Avalonia.Controls/Repeater/ViewManager.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index 7d005a30b4..4ed9cb3333 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -388,19 +388,24 @@ namespace Avalonia.Controls } case NotifyCollectionChangedAction.Reset: - if (_owner.ItemsSourceView.HasKeyIndexMapping) + // If we get multiple resets back to back before + // running layout, we dont have to clear all the elements again. + if (!_isDataSourceStableResetPending) { - _isDataSourceStableResetPending = true; - } + if (_owner.ItemsSourceView.HasKeyIndexMapping) + { + _isDataSourceStableResetPending = true; + } - // Walk through all the elements and make sure they are cleared, they will go into - // the stable id reset pool. - foreach (var element in _owner.Children) - { - var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate) + // Walk through all the elements and make sure they are cleared, they will go into + // the stable id reset pool. + foreach (var element in _owner.Children) { - _owner.ClearElementImpl(element); + var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); + if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate) + { + _owner.ClearElementImpl(element); + } } } @@ -441,6 +446,9 @@ namespace Avalonia.Controls } _resetPool.Clear(); + + // Flush the realized indices once the stable reset pool is cleared to start fresh. + InvalidateRealizedIndicesHeldByLayout(); } } @@ -498,6 +506,10 @@ namespace Avalonia.Controls var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); virtInfo.MoveOwnershipToLayoutFromUniqueIdResetPool(); UpdateElementIndex(element, virtInfo, index); + + // Update realized indices + _firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index); + _lastRealizedElementIndexHeldByLayout = Math.Max(_lastRealizedElementIndexHeldByLayout, index); } } @@ -519,6 +531,10 @@ namespace Avalonia.Controls _pinnedPool.RemoveAt(i); element = elementInfo.PinnedElement; elementInfo.VirtualizationInfo.MoveOwnershipToLayoutFromPinnedPool(); + + // Update realized indices + _firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index); + _lastRealizedElementIndexHeldByLayout = Math.Max(_lastRealizedElementIndexHeldByLayout, index); break; } } From 310d594461463a0010f9cee4c65391cee2577be5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 08:34:33 +0200 Subject: [PATCH 3/5] StackLayout incorrect sizing bug Ported from https://github.com/microsoft/microsoft-ui-xaml/commit/c621498e61909850f1d7d0e760a296355f75d58f --- src/Avalonia.Layout/StackLayout.cs | 4 ++-- src/Avalonia.Layout/StackLayoutState.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index b383c205f9..d11875b2c1 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -277,6 +277,8 @@ namespace Avalonia.Layout protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { + ((StackLayoutState)context.LayoutState).OnMeasureStart(); + var desiredSize = GetFlowAlgorithm(context).Measure( availableSize, context, @@ -300,8 +302,6 @@ namespace Avalonia.Layout FlowLayoutAlgorithm.LineAlignment.Start, LayoutId); - ((StackLayoutState)context.LayoutState).OnArrangeLayoutEnd(); - return new Size(value.Width, value.Height); } diff --git a/src/Avalonia.Layout/StackLayoutState.cs b/src/Avalonia.Layout/StackLayoutState.cs index 05ad9bca8e..e6164e02e6 100644 --- a/src/Avalonia.Layout/StackLayoutState.cs +++ b/src/Avalonia.Layout/StackLayoutState.cs @@ -56,6 +56,6 @@ namespace Avalonia.Layout MaxArrangeBounds = Math.Max(MaxArrangeBounds, minorSize); } - internal void OnArrangeLayoutEnd() => MaxArrangeBounds = 0; + internal void OnMeasureStart() => MaxArrangeBounds = 0; } } From c3635302b5c142d21c8ff5f222b981263464176a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 14 May 2020 13:03:32 +0300 Subject: [PATCH 4/5] Changed box shadow drawing APIs a bit --- src/Avalonia.Visuals/Media/BoxShadows.cs | 4 +--- src/Avalonia.Visuals/Media/DrawingContext.cs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Media/BoxShadows.cs b/src/Avalonia.Visuals/Media/BoxShadows.cs index fd187f6409..9e4d6aacb0 100644 --- a/src/Avalonia.Visuals/Media/BoxShadows.cs +++ b/src/Avalonia.Visuals/Media/BoxShadows.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media { _first = shadow; _list = null; - Count = 1; + Count = _first.IsEmpty ? 0 : 1; } public BoxShadows(BoxShadow first, BoxShadow[] rest) @@ -105,8 +105,6 @@ namespace Avalonia.Media return false; } } - - public static implicit operator BoxShadows(BoxShadow shadow) => new BoxShadows(shadow); public bool Equals(BoxShadows other) { diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 4df26c470d..1a45b8342a 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -141,13 +141,13 @@ namespace Avalonia.Media /// The radius in the Y dimension of the rounded corners. /// This value will be clamped to the range of 0 to Height/2 /// - /// Box shadow effect parameters + /// Box shadow effect parameters /// /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. /// public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX = 0, double radiusY = 0, - BoxShadow boxShadow = default) + BoxShadows boxShadows = default) { if (brush == null && !PenIsVisible(pen)) { @@ -164,7 +164,7 @@ namespace Avalonia.Media radiusY = Math.Min(radiusY, rect.Height / 2); } - PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadow); + PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows); } /// From 949f33227f2913369b671be18611df64cf34c907 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 22:00:49 +0200 Subject: [PATCH 5/5] Unwrap TargetInvocationException. To provide a better error message to the designer. --- .../Remote/RemoteDesignerEntryPoint.cs | 10 +------- .../DesignMessages.cs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 44d9a94f5a..e61fe82c41 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -234,18 +234,10 @@ namespace Avalonia.DesignerSupport.Remote } catch (Exception e) { - var xmlException = e as XmlException; - s_transport.Send(new UpdateXamlResultMessage { Error = e.ToString(), - Exception = new ExceptionDetails - { - ExceptionType = e.GetType().FullName, - Message = e.Message.ToString(), - LineNumber = xmlException?.LineNumber, - LinePosition = xmlException?.LinePosition, - } + Exception = new ExceptionDetails(e), }); } } diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs index 5ff16c574d..5c769ad48c 100644 --- a/src/Avalonia.Remote.Protocol/DesignMessages.cs +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -1,4 +1,7 @@ using System; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Xml; namespace Avalonia.Remote.Protocol.Designer { @@ -26,6 +29,27 @@ namespace Avalonia.Remote.Protocol.Designer public class ExceptionDetails { + public ExceptionDetails() + { + } + + public ExceptionDetails(Exception e) + { + if (e is TargetInvocationException) + { + e = e.InnerException; + } + + ExceptionType = e.GetType().Name; + Message = e.Message; + + if (e is XmlException xml) + { + LineNumber = xml.LineNumber; + LinePosition = xml.LinePosition; + } + } + public string ExceptionType { get; set; } public string Message { get; set; } public int? LineNumber { get; set; }