From 69bd34e2160d0107968144282e8bc3206db784a2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 24 Apr 2020 18:32:31 -0400 Subject: [PATCH] feat(itemsrepeater): Brings more C++ code and begin integration with Uno --- src/Uno.UI/UI/Xaml/Controls/Panel/Panel.cs | 3 +- .../Primitives/Repeater/BuildTreeScheduler.cs | 64 +++++ .../ChildrenInTabFocusOrderIterable.cs | 82 ++++++ .../Primitives/Repeater/ElementManager.cs | 2 +- .../Controls/Primitives/Repeater/IPanel.cs | 19 ++ .../Repeater/ItemTemplateWrapper.cs | 217 ++++++++-------- .../Primitives/Repeater/ItemsRepeater.cs | 15 +- .../Controls/Primitives/Repeater/Layout.cs | 14 +- .../Primitives/Repeater/LayoutContext.cs | 2 +- .../Repeater/LayoutContextAdapter.cs | 180 ++++++------- .../Primitives/Repeater/ListAdapter.cs | 68 +++++ .../Repeater/NonvirtualizingLayoutContext.cs | 26 +- .../Controls/Primitives/Repeater/Phaser.cs | 243 ++++++++++++++++++ .../Primitives/Repeater/RecyclePool.cs | 120 +++++++++ .../Primitives/Repeater/RecyclePoolFactory.cs | 84 ++++++ .../Repeater/RepeaterLayoutContext.cs | 77 ++++++ .../Repeater/UniqueIdElementPool.cs | 58 +++++ .../Primitives/Repeater/ViewManager.cs | 165 +++++++----- ...LayoutContextAdapter.ChildrenCollection.cs | 78 ++++++ .../Repeater/VirtualLayoutContextAdapter.cs | 55 ++++ .../Primitives/Repeater/VirtualizationInfo.cs | 17 +- .../Repeater/VirtualizingLayoutContext.cs | 12 +- src/Uno.UI/UI/Xaml/DataTemplateSelector.cs | 8 +- .../UI/Xaml/UIElementCollection.Android.cs | 2 + 24 files changed, 1278 insertions(+), 333 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/BuildTreeScheduler.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ChildrenInTabFocusOrderIterable.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/IPanel.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Phaser.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePool.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePoolFactory.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RepeaterLayoutContext.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/UniqueIdElementPool.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.ChildrenCollection.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/Panel/Panel.cs b/src/Uno.UI/UI/Xaml/Controls/Panel/Panel.cs index 3f1a7ecfd464..43aa4ef3d676 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Panel/Panel.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Panel/Panel.cs @@ -9,6 +9,7 @@ using Uno.UI.DataBinding; using Windows.UI.Core; using Windows.UI.Xaml.Media; +using Microsoft.UI.Xaml.Controls; #if XAMARIN_ANDROID using View = Android.Views.View; @@ -21,7 +22,7 @@ namespace Windows.UI.Xaml.Controls { [Markup.ContentProperty(Name = "Children")] - public partial class Panel : FrameworkElement, ICustomClippingElement + public partial class Panel : FrameworkElement, ICustomClippingElement, IPanel { #if NET461 || NETSTANDARD2_0 private new UIElementCollection _children; diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/BuildTreeScheduler.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/BuildTreeScheduler.cs new file mode 100644 index 000000000000..d6746398f4cb --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/BuildTreeScheduler.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "pch.h" +#include "common.h" +#include "QPCTimer.h" +#include "BuildTreeScheduler.h" +#include "RepeaterTestHooks.h" + +double BuildTreeScheduler.m_budgetInMs = 40.0; +thread_local QPCTimer BuildTreeScheduler.m_timer{}; +thread_local std.CalculatorList BuildTreeScheduler.m_pendingWork{}; +thread_local event_token BuildTreeScheduler.m_renderingToken{}; + +void RegisterWork(int priority, const std.function& workFunc) +{ + global::System.Diagnostics.Debug.Assert(priority >= 0); + global::System.Diagnostics.Debug.Assert(workFunc != null); + + QueueTick(); + m_pendingWork.push_back(WorkInfo(priority, workFunc)); +} + +bool ShouldYield() +{ + return m_timer.DurationInMilliSeconds() > m_budgetInMs; +} + +void OnRendering(const IInspectable&, const IInspectable&) +{ + bool budgetReached = ShouldYield(); + if (!budgetReached && m_pendingWork.size() > 0) + { + // Sort in descending order of priority and work from the end of the list to avoid moving around during erase. + std.sort(m_pendingWork.begin(), m_pendingWork.end(), [](const auto& lhs, const auto& rhs) { return lhs.Priority() > rhs.Priority(); }); + int currentIndex = (int)(m_pendingWork.size()) - 1; + + do + { + m_pendingWork[currentIndex].InvokeWorkFunc(); + m_pendingWork.erase(m_pendingWork.begin() + currentIndex); + } while (--currentIndex >= 0 && !ShouldYield()); + } + + if (m_pendingWork.empty()) + { + // No more pending work, unhook from rendering event since being hooked up will case wux to try to + // call the event at 60 frames per second + Windows.UI.Xaml.Media.CompositionTarget.CompositionTarget.Rendering(m_renderingToken); + m_renderingToken.value = 0; + RepeaterTestHooks.NotifyBuildTreeCompleted(); + } + + // Reset the timer so it snaps the time just before rendering + m_timer.Reset(); +} + +void QueueTick() +{ + if (m_renderingToken.value == 0) + { + m_renderingToken = Windows.UI.Xaml.Media.CompositionTarget.Rendering(OnRendering); + } +} \ No newline at end of file diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ChildrenInTabFocusOrderIterable.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ChildrenInTabFocusOrderIterable.cs new file mode 100644 index 000000000000..6a3a38267f50 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ChildrenInTabFocusOrderIterable.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +using System; +using System.Collections; +using System.Collections.Generic; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml.Controls +{ + internal class ChildrenInTabFocusOrderIterable : IEnumerable + { + private readonly ItemsRepeater m_repeater; + + public ChildrenInTabFocusOrderIterable(ItemsRepeater repeater) + { + m_repeater = repeater; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() => new ChildrenInTabFocusOrderIterator(m_repeater); + + private class ChildrenInTabFocusOrderIterator : IEnumerator + { + private readonly List> m_realizedChildren; + private int m_index = 0; + + public ChildrenInTabFocusOrderIterator(ItemsRepeater repeater) + { + var children = repeater.Children; + m_realizedChildren = new List>(children.Count); + + // Filter out unrealized children. + for (var i = 0; i < children.Count; ++i) + { + var element = children[i]; + var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); + if (virtInfo.IsRealized) + { + m_realizedChildren.Add(new KeyValuePair(virtInfo.Index, element)); + } + } + + // Sort children by index. + m_realizedChildren.Sort((lhs, rhs) => lhs.Key - rhs.Key); + } + + object IEnumerator.Current => Current; + public DependencyObject Current + { + get + { + if (m_index < m_realizedChildren.Count) + { + return m_realizedChildren[m_index].Value; + } + else + { + throw new IndexOutOfRangeException(); + } + } + } + + public bool MoveNext() + { + if (m_index < m_realizedChildren.Count) + { + ++m_index; + return m_index < m_realizedChildren.Count; + } + else + { + throw new IndexOutOfRangeException(); + } + } + + public void Reset() { } + public void Dispose() { } + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ElementManager.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ElementManager.cs index df49329038b1..55b5c13f7ac1 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ElementManager.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ElementManager.cs @@ -47,7 +47,7 @@ private bool IsVirtualizingContext if (m_context != null) { var rect = m_context.RealizationRect; - bool hasInfiniteSize = double.IsInfinity(rect.Height) || double.IsInfinity(rect.Width)); + bool hasInfiniteSize = double.IsInfinity(rect.Height) || double.IsInfinity(rect.Width); return !hasInfiniteSize; } diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/IPanel.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/IPanel.cs new file mode 100644 index 000000000000..fcc8045f781d --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/IPanel.cs @@ -0,0 +1,19 @@ +using System; +using System.Linq; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml.Controls +{ + /// + /// This interface allows use to replicate the "DeriveFromPanelHelper_base" of WinUI (cf. Remarks) + /// + /// + /// Doc about the DeriveFromPanelHelper_base in WinUI: + /// This type exists for types that in metadata derive from FrameworkElement but internally want to derive from Panel + /// to get "protected" Children. + /// + internal interface IPanel + { + public UIElementCollection Children { get; } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemTemplateWrapper.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemTemplateWrapper.cs index d48824d56cda..920d3ea881ab 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemTemplateWrapper.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemTemplateWrapper.cs @@ -1,114 +1,113 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Shapes; -#include "pch.h" -#include "ItemTemplateWrapper.h" -#include "RecyclePool.h" -#include "ItemsRepeater.common.h" - -ItemTemplateWrapper(DataTemplate const& dataTemplate) -{ - m_dataTemplate = dataTemplate; -} - -ItemTemplateWrapper(DataTemplateSelector const& dataTemplateSelector) -{ - m_dataTemplateSelector = dataTemplateSelector; -} - -DataTemplate Template() -{ - return m_dataTemplate; -} - -void Template(DataTemplate const& value) -{ - m_dataTemplate = value; -} - -DataTemplateSelector TemplateSelector() -{ - return m_dataTemplateSelector; -} - -void TemplateSelector(DataTemplateSelector const& value) -{ - m_dataTemplateSelector = value; -} - -#region IElementFactory - -UIElement GetElement(ElementFactoryGetArgs const& args) -{ - var selectedTemplate = m_dataTemplate ? m_dataTemplate : m_dataTemplateSelector.SelectTemplate(args.Data()); - // Check if selected template we got is valid - if (selectedTemplate == null) - { - // Null template, use other SelectTemplate method - try - { - selectedTemplate = m_dataTemplateSelector.SelectTemplate(args.Data(), null); - } - catch (hresult_error e) - { - // The default implementation of SelectTemplate(IInspectable item, ILayout container) throws invalid arg for null container - // To not force everbody to provide an implementation of that, catch that here - if (e.code().value != E_INVALIDARG) - { - throw e; - } - } - - if (selectedTemplate == null) - { - // Still null, fail with a reasonable message now. - throw hresult_invalid_argument("Null encountered as data template. That is not a valid value for a data template, and can not be used."); - } - } - var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); - UIElement element = null; - - if (recyclePool) - { - // try to get an element from the recycle pool. -( element = recyclePool.TryGetElement("" /* key */, args.Parent() as FrameworkElement)); - } - - if (!element) - { - // no element was found in recycle pool, create a new element -( element = selectedTemplate.LoadContent() as FrameworkElement); - - // Template returned null, so insert empty element to render nothing - if (!element) { - var rectangle = Rectangle(); - rectangle.Width(0); - rectangle.Height(0); - element = rectangle; - } - - // Associate template with element - element.SetValue(RecyclePool.GetOriginTemplateProperty(), selectedTemplate); - } - - return element; -} - -void RecycleElement(ElementFactoryRecycleArgs const& args) +namespace Microsoft.UI.Xaml.Controls { - var element = args.Element(); - DataTemplate selectedTemplate = m_dataTemplate? - m_dataTemplate: -( element.GetValue(RecyclePool.GetOriginTemplateProperty()) as DataTemplate); - var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); - if (!recyclePool) - { - // No Recycle pool in the template, create one. - recyclePool = new RecyclePool(); - RecyclePool.SetPoolInstance(selectedTemplate, recyclePool); - } - - recyclePool.PutElement(args.Element(), "" /* key */, args.Parent()); + internal class ItemTemplateWrapper : IElementFactory, IElementFactoryShim + { + private DataTemplate m_dataTemplate; + private DataTemplateSelector m_dataTemplateSelector; + + public ItemTemplateWrapper(DataTemplate dataTemplate) + { + m_dataTemplate = dataTemplate; + } + + public ItemTemplateWrapper(DataTemplateSelector dataTemplateSelector) + { + m_dataTemplateSelector = dataTemplateSelector; + } + + DataTemplate Template + { + get => m_dataTemplate; + set => m_dataTemplate = value; + } + + DataTemplateSelector TemplateSelector + { + get => m_dataTemplateSelector; + set => m_dataTemplateSelector = value; + } + + #region IElementFactory + public UIElement GetElement(ElementFactoryGetArgs args) + { + var selectedTemplate = m_dataTemplate ?? m_dataTemplateSelector.SelectTemplate(args.Data); + // Check if selected template we got is valid + if (selectedTemplate == null) + { + // Null template, use other SelectTemplate method + try + { + selectedTemplate = m_dataTemplateSelector.SelectTemplate(args.Data, null); + } + catch (ArgumentException) + { + // The default implementation of SelectTemplate(IInspectable item, ILayout container) throws invalid arg for null container + // To not force everbody to provide an implementation of that, catch that here + //if (e.code().value != E_INVALIDARG) + //{ + // throw e; + //} + } + + if (selectedTemplate == null) + { + // Still null, fail with a reasonable message now. + throw new ArgumentException("Null encountered as data template. That is not a valid value for a data template, and can not be used."); + } + } + + var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); + UIElement element = null; + + if (recyclePool != null) + { + // try to get an element from the recycle pool. + element = recyclePool.TryGetElement("" /* key */, args.Parent as FrameworkElement); + } + + if (element == null) + { + // no element was found in recycle pool, create a new element + element = selectedTemplate.LoadContent() as FrameworkElement; + + // Template returned null, so insert empty element to render nothing + if (element != null) + { + var rectangle = new Rectangle(); + rectangle.Width = 0; + rectangle.Height = 0; + element = rectangle; + } + + // Associate template with element + element.SetValue(RecyclePool.GetOriginTemplateProperty(), selectedTemplate); + } + + return element; + } + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + var element = args.Element; + DataTemplate selectedTemplate = m_dataTemplate ?? element.GetValue(RecyclePool.GetOriginTemplateProperty()) as DataTemplate; + var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); + if (recyclePool == null) + { + // No Recycle pool in the template, create one. + recyclePool = new RecyclePool(); + RecyclePool.SetPoolInstance(selectedTemplate, recyclePool); + } + + recyclePool.PutElement(args.Element, "" /* key */, args.Parent); + } + + #endregion + } } - -#endregion diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemsRepeater.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemsRepeater.cs index 37a222a75428..9ac5a5a60680 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemsRepeater.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ItemsRepeater.cs @@ -19,10 +19,11 @@ namespace Microsoft.UI.Xaml.Controls { - public partial class ItemsRepeater : FrameworkElement + public partial class ItemsRepeater : FrameworkElement, IPanel { - // TODO UNO Oh oh - internal List Children; + private readonly UIElementCollection _children; + UIElementCollection IPanel.Children => _children; + internal IList Children => _children.AsUIElementList(); // Change to 'true' to turn on debugging outputs in Output window @@ -167,6 +168,8 @@ private static void OnPropertyChanged(DependencyObject snd, DependencyPropertyCh public ItemsRepeater() { + _children = new UIElementCollection(this); + //__RP_Marker_ClassById(RuntimeProfiler.ProfId_ItemsRepeater); //if (SharedHelpers.IsRS5OrHigher()) @@ -394,13 +397,13 @@ UIElement GetOrCreateElement(int index) #endregion - private UIElement GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle) + public UIElement GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle) { var element = m_viewManager.GetElement(index, forceCreate, suppressAutoRecycle); return element; } - void ClearElementImpl(UIElement element) + public void ClearElementImpl(UIElement element) { // Clearing an element due to a collection change // is more strict in that pinned elements will be forcibly @@ -604,7 +607,7 @@ void OnElementIndexChanged(UIElement element, int oldIndex, int newIndex) // Provides an indentation based on repeater elements in the UI Tree that // can be used to make logging a little easier to read. - int Indent() + internal int Indent() { int indent = 1; diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Layout.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Layout.cs index 597a5d5fd1e2..4dff8e531b5f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Layout.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Layout.cs @@ -76,12 +76,11 @@ public Size Measure(LayoutContext context, Size availableSize) switch (this) { case VirtualizingLayout virtualizingLayout: - virtualizingLayout.MeasureOverride(GetVirtualizingLayoutContext(context), availableSize); - break; + return virtualizingLayout.MeasureOverride(GetVirtualizingLayoutContext(context), availableSize); case NonVirtualizingLayout nonVirtualizingLayout: - nonVirtualizingLayout.MeasureOverride(GetNonVirtualizingLayoutContext(context), availableSize); - break; + return nonVirtualizingLayout.MeasureOverride(GetNonVirtualizingLayoutContext(context), availableSize); + default: throw new NotImplementedException(); } @@ -92,12 +91,11 @@ public Size Arrange(LayoutContext context, Size finalSize) switch (this) { case VirtualizingLayout virtualizingLayout: - virtualizingLayout.ArrangeOverride(GetVirtualizingLayoutContext(context), finalSize); - break; + return virtualizingLayout.ArrangeOverride(GetVirtualizingLayoutContext(context), finalSize); case NonVirtualizingLayout nonVirtualizingLayout: - nonVirtualizingLayout.ArrangeOverride(GetNonVirtualizingLayoutContext(context), finalSize); - break; + return nonVirtualizingLayout.ArrangeOverride(GetNonVirtualizingLayoutContext(context), finalSize); + default: throw new NotImplementedException(); } diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContext.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContext.cs index 2c2d2a0d800e..a52c796d95bf 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContext.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContext.cs @@ -8,7 +8,7 @@ namespace Microsoft.UI.Xaml.Controls public class LayoutContext { #region ILayoutContext - private object LayoutState + public object LayoutState { get => LayoutStateCore; set => LayoutStateCore = value; diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContextAdapter.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContextAdapter.cs index e41e74c67567..9addea33badc 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContextAdapter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/LayoutContextAdapter.cs @@ -1,110 +1,82 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -#include -#include -#include "ItemsRepeater.common.h" -#include "VirtualizingLayoutContext.h" -#include "LayoutContextAdapter.h" +using System; +using Windows.Foundation; +using Windows.UI.Xaml; -LayoutContextAdapter(NonVirtualizingLayoutContext const& nonVirtualizingContext) +namespace Microsoft.UI.Xaml.Controls { - m_nonVirtualizingContext = make_weak(nonVirtualizingContext); + internal class LayoutContextAdapter : VirtualizingLayoutContext + { + private readonly WeakReference m_nonVirtualizingContext; + + public LayoutContextAdapter(NonVirtualizingLayoutContext nonVirtualizingContext) + { + m_nonVirtualizingContext = new WeakReference(nonVirtualizingContext); + } + + #region ILayoutContextOverrides + protected override object LayoutStateCore + { + get => m_nonVirtualizingContext.TryGetTarget(out var context) ? context.LayoutState : null; + set + { + if (m_nonVirtualizingContext.TryGetTarget(out var context)) + { + context.LayoutState = value; + } + } + } + + #endregion + + #region IVirtualizingLayoutContextOverrides + + protected override int ItemCountCore => m_nonVirtualizingContext.TryGetTarget(out var context) ? context.Children.Count : 0; + + protected override object GetItemAtCore(int index) + => m_nonVirtualizingContext.TryGetTarget(out var context) ? context.Children[index] : null; + + protected override UIElement GetOrCreateElementAtCore(int index, ElementRealizationOptions options) + => m_nonVirtualizingContext.TryGetTarget(out var context) ? context.Children[index] : null; + + protected override void RecycleElementCore(UIElement element) + { + } + + private int GetElementIndexCore(UIElement element) + { + if (m_nonVirtualizingContext.TryGetTarget(out var context)) + { + var children = context.Children; + for (int i = 0; i < children.Count; i++) + { + if (children[i] == element) + { + return i; + } + } + } + + return -1; + } + + protected override Rect RealizationRectCore => new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); + + protected override int RecommendedAnchorIndexCore => -1; + + protected override Point LayoutOriginCore + { + get => new Point(0, 0); + set + { + if (value != new Point(0, 0)) + { + throw new ArgumentException("LayoutOrigin must be at (0,0) when RealizationRect is infinite sized."); + } + } + } + #endregion + } } - -#region ILayoutContextOverrides - -IInspectable LayoutStateCore() -{ - if (var context = m_nonVirtualizingContext.get()) - { - return context.LayoutState(); - } - return null; -} - -void LayoutStateCore(IInspectable const& state) -{ - if (var context = m_nonVirtualizingContext.get()) - { - context.LayoutStateCore(state); - } -} - -#endregion - -#region IVirtualizingLayoutContextOverrides - -int ItemCountCore() -{ - if (var context = m_nonVirtualizingContext.get()) - { - return context.Children().Size(); - } - return 0; -} - -IInspectable GetItemAtCore(int index) -{ - if (var context = m_nonVirtualizingContext.get()) - { - return context.Children().GetAt(index); - } - return null; -} - -UIElement GetOrCreateElementAtCore(int index, ElementRealizationOptions const& options) -{ - if (var context = m_nonVirtualizingContext.get()) - { - return context.Children().GetAt(index); - } - return null; -} - -void RecycleElementCore(UIElement const& element) -{ - -} - -int GetElementIndexCore(UIElement const& element) -{ - if (var context = m_nonVirtualizingContext.get()) - { - var children = context.Children(); - for (uint i = 0; i < children.Size(); i++) - { - if (children.GetAt(i) == element) - { - return i; - } - } - } - - return -1; -} - -Rect RealizationRectCore() -{ - return Rect{ 0, 0, std.numeric_limits.infinity(), std.numeric_limits.infinity() }; -} - -int RecommendedAnchorIndexCore() -{ - return -1; -} - -Point LayoutOriginCore() -{ - return Point(0, 0); -} - -void LayoutOriginCore(Point const& value) -{ - if (value != Point(0, 0)) - { - throw hresult_invalid_argument("LayoutOrigin must be at (0,0) when RealizationRect is infinite sized."); - } -} - -#endregion diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ListAdapter.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ListAdapter.cs index 6ac29b78eb48..9d3db4115743 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ListAdapter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ListAdapter.cs @@ -2,16 +2,84 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace Microsoft.UI.Xaml.Controls { internal class ListAdapter { + /// + /// Creates a typed list decorator that changes the type of the source list + /// + /// This is the equivalent of the Linq Cast operator. + public static IList ChangeType(IList list) => new CastList(list); + + /// + /// Creates a typed list decorator over an IList + /// + /// This is the equivalent of the Linq Cast operator. public static IList ToGeneric(IList list) => new GenericList(list); + + /// + /// Creates a typed list decorator over an IList + /// + /// This is the equivalent of the Linq Cast operator. public static IList ToGeneric(IList list) => new GenericList(list); + /// + /// Creates an untyped list decorator over a generic list + /// public static IList ToUntyped(IList list) => new UntypedList(list); + private class CastList : IList + { + private readonly IList _inner; + + public CastList(IList inner) + { + _inner = inner; + } + + public TTarget this[int index] + { + get => To(_inner[index]); + set => _inner[index] = From(value); + } + public int Count => _inner.Count; + public bool IsReadOnly => _inner.IsReadOnly; + + public void Add(TTarget item) => _inner.Add(From(item)); + public void Insert(int index, TTarget item) => _inner.Insert(index, From(item)); + public bool Contains(TTarget item) => _inner.Contains(From(item)); + public int IndexOf(TTarget item) => _inner.IndexOf(From(item)); + public void RemoveAt(int index) => _inner.RemoveAt(index); + public bool Remove(TTarget item) + { + if (_inner.Contains(From(item))) + { + _inner.Remove(From(item)); + return true; + } + else + { + return false; + } + } + public void Clear() => _inner.Clear(); + public void CopyTo(TTarget[] array, int arrayIndex) + { + var length = array.Length - arrayIndex; + var tmp = new TSource[length]; + _inner.CopyTo(tmp, 0); + Array.Copy(tmp, 0, array, arrayIndex, length); + } + public IEnumerator GetEnumerator() => new GenericEnumerator(_inner.GetEnumerator()); + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + + private static TSource From(object value) => (TSource)value; + private static TTarget To(object value) => (TTarget)value; + } + private class GenericList : IList { private readonly IList _inner; diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/NonvirtualizingLayoutContext.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/NonvirtualizingLayoutContext.cs index 80acadca8f0e..c03e59b07a19 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/NonvirtualizingLayoutContext.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/NonvirtualizingLayoutContext.cs @@ -1,33 +1,25 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using System; +using System.Collections.Generic; +using Windows.UI.Xaml; + namespace Microsoft.UI.Xaml.Controls { public class NonVirtualizingLayoutContext : LayoutContext { - #region INonVirtualizingLayoutContext - - IVectorView NonVirtualizingLayoutContext.Children() - { - return overridable().ChildrenCore(); - } + private LayoutContextAdapter m_contextAdapter; - #endregion - - #region INonVirtualizingLayoutContextOverrides - - IVectorView NonVirtualizingLayoutContext.ChildrenCore() - { - throw hresult_not_implemented(); - } + public IReadOnlyList Children => ChildrenCore; - #endregion + protected virtual IReadOnlyList ChildrenCore => throw new NotImplementedException(); internal VirtualizingLayoutContext GetVirtualizingContextAdapter() { - if (!m_contextAdapter) + if (m_contextAdapter == null) { - (m_contextAdapter = new LayoutContextAdapter(get_strong() as NonVirtualizingLayoutContext)); + m_contextAdapter = new LayoutContextAdapter(this); } return m_contextAdapter; diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Phaser.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Phaser.cs new file mode 100644 index 000000000000..240a69d693ff --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/Phaser.cs @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Markup; +using Uno.UI.Helpers.WinUI; + +namespace Microsoft.UI.Xaml.Controls +{ + internal class Phaser + { + private readonly ItemsRepeater m_owner; + private List m_pendingElements = new List(); + private bool m_registeredForCallback; + + public Phaser(ItemsRepeater owner) + { + // ItemsRepeater is not fully constructed yet. Don't interact with it. + + m_owner = owner; + } + + void PhaseElement(UIElement element, VirtualizationInfo virtInfo) + { + var dataTemplateComponent = virtInfo.DataTemplateComponent; + var nextPhase = virtInfo.Phase; + bool shouldPhase = false; + + if (nextPhase > 0) + { + if (dataTemplateComponent != null) + { + // Perf optimization: RecyclingElementFactory sets up the virtualization info so we don't need to update it here. + shouldPhase = true; + } + else + { + throw new InvalidOperationException("Phase was set on virtualization info, but dataTemplateComponent was not."); + } + } + else if (nextPhase == VirtualizationInfo.PhaseNotSpecified) + { + // If virtInfo.Phase is not specified, virtInfo.DataTemplateComponent cannot be valid. + global::System.Diagnostics.Debug.Assert(dataTemplateComponent == null); + // ItemsRepeater might be using a custom view generator in which case, virtInfo would not be bootstrapped. + // In this case, fallback to querying for the data template component and setup virtualization info now. + dataTemplateComponent = XamlBindingHelper.GetDataTemplateComponent(element); + if (dataTemplateComponent != null) + { + // Clear out old data. + dataTemplateComponent.Recycle(); + + nextPhase = VirtualizationInfo.PhaseReachedEnd; + var index = virtInfo.Index; + var data = m_owner.ItemsSourceView.GetAt(index); + // Run Phase 0 + dataTemplateComponent.ProcessBindings(data, index, 0 /* currentPhase */, out nextPhase); + + // Update phase on virtInfo. Set data and templateComponent only if x:Phase was used. + virtInfo.UpdatePhasingInfo(nextPhase, nextPhase > 0 ? data : null, nextPhase > 0 ? dataTemplateComponent : null); + shouldPhase = nextPhase > 0; + } + } + + if (shouldPhase) + { + // Insert at the top since we remove from bottom during DoPhasedWorkCallback. This keeps the ordering of items + // the same as the order in which items are realized. + m_pendingElements.Insert(0, new ElementInfo(element, virtInfo)); + RegisterForCallback(); + } + } + + void StopPhasing(UIElement element, VirtualizationInfo virtInfo) + { + // We need to remove the element from the pending elements list. We cannot just change the phase to -1 + // since it will get updated when the element gets recycled. + if (virtInfo.DataTemplateComponent != null) + { + var it = m_pendingElements.FindIndex(info => info.Element == element); + if (it != -1) + { + m_pendingElements.RemoveAt(it); + } + } + + // Clean Phasing information for this item. + virtInfo.UpdatePhasingInfo(VirtualizationInfo.PhaseNotSpecified, null /* data */, null /* dataTemplateComponent */); + } + + void DoPhasedWorkCallback() + { + MarkCallbackRecieved(); + + if (m_pendingElements.Count > 0 && !BuildTreeScheduler.ShouldYield()) + { + var visibleWindow = m_owner.VisibleWindow; + SortElements(visibleWindow); + int currentIndex = m_pendingElements.Count - 1; + do + { + var info = m_pendingElements[currentIndex]; + var element = info.Element; + var virtInfo = info.VirtInfo; + var dataIndex = virtInfo.Index; + + int currentPhase = virtInfo.Phase; + if (currentPhase > 0) + { + int nextPhase = VirtualizationInfo.PhaseReachedEnd; + virtInfo.DataTemplateComponent.ProcessBindings(virtInfo.Data, -1 /* item index unused */, currentPhase, nextPhase); + ValidatePhaseOrdering(currentPhase, nextPhase); + + var previousAvailableSize = LayoutInformation.GetAvailableSize(element); + element.Measure(previousAvailableSize); + + if (nextPhase > 0) + { + virtInfo.Phase = nextPhase; + // If we are the first item or + // If the current items phase is higher than the next items phase, then move to the next item. + if (currentIndex == 0 || + virtInfo.Phase > m_pendingElements[currentIndex - 1].VirtInfo.Phase) + { + currentIndex--; + } + } + else + { + m_pendingElements.RemoveAt(currentIndex); + currentIndex--; + } + } + else + { + throw new InvalidOperationException("Cleared element found in pending list which is not expected"); + } + + var pendingCount = (int)(m_pendingElements.Count); + if (currentIndex == -1) + { + // Reached the top, start from the bottom again + currentIndex = pendingCount - 1; + } + else if (currentIndex > -1 && currentIndex < pendingCount - 1) + { + // If the next element is oustide the visible window and there are elements in the visible window + // go back to the visible window. + bool nextItemIsVisible = SharedHelpers.DoRectsIntersect(visibleWindow, m_pendingElements[currentIndex].VirtInfo().ArrangeBounds()); + if (!nextItemIsVisible) + { + bool haveVisibleItems = SharedHelpers.DoRectsIntersect(visibleWindow, m_pendingElements[pendingCount - 1].VirtInfo().ArrangeBounds()); + if (haveVisibleItems) + { + currentIndex = pendingCount - 1; + } + } + } + } while (m_pendingElements.Count > 0 && !BuildTreeScheduler.ShouldYield()); + } + + if (m_pendingElements.Count > 0) + { + RegisterForCallback(); + } + } + + void RegisterForCallback() + { + if (!m_registeredForCallback) + { + global::System.Diagnostics.Debug.Assert(!m_pendingElements.empty()); + m_registeredForCallback = true; + BuildTreeScheduler.RegisterWork( + m_pendingElements[m_pendingElements.size() - 1].VirtInfo().Phase(), // Use the phase of the last one in the sorted list + [this]() + { + DoPhasedWorkCallback(); + }); + } + } + + void MarkCallbackRecieved() + { + m_registeredForCallback = false; + } + + /* static */ + void ValidatePhaseOrdering(int currentPhase, int nextPhase) + { + if (nextPhase > 0 && nextPhase <= currentPhase) + { + // nextPhase <= currentPhase is invalid + throw new InvalidOperationException("Phases are required to be monotonically increasing."); + } + } + + void SortElements(Rect visibleWindow) + { + // Sort in descending order (inVisibleWindow, phase) + m_pendingElements.Sort((lhs, rhs) => + { + var lhsBounds = lhs.VirtInfo.ArrangeBounds; + var lhsIntersects = SharedHelpers.DoRectsIntersect(lhsBounds, visibleWindow); + var rhsBounds = rhs.VirtInfo.ArrangeBounds; + var rhsIntersects = SharedHelpers.DoRectsIntersect(rhsBounds, visibleWindow); + + if ((lhsIntersects && rhsIntersects) || + (!lhsIntersects && !rhsIntersects)) + { + // Both are in the visible window or both are not + return lhs.VirtInfo.Phase - rhs.VirtInfo.Phase; + } + else if (lhsIntersects) + { + // Left is in visible window + return -1; + } + else + { + return 1; + } + }); + } + + private struct ElementInfo + { + public ElementInfo(UIElement element, VirtualizationInfo virtInfo) + { + Element = element; + VirtInfo = virtInfo; + } + + public UIElement Element { get; } + public VirtualizationInfo VirtInfo { get; } + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePool.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePool.cs new file mode 100644 index 000000000000..dab45b3abd27 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePool.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls +{ + internal class RecyclePool + { + private readonly Dictionary> m_elements = new Dictionary>(); + + private struct ElementInfo + { + public ElementInfo(UIElement element, IPanel owner) + { + Element = element; + Owner = owner; + } + + public UIElement Element { get; } + public IPanel Owner { get; } + }; + + public void PutElement(UIElement element, string key) + => PutElementCore(element, key, null /* owner */); + + public void PutElement(UIElement element, string key, UIElement owner) + => PutElementCore(element, key, owner); + + public UIElement TryGetElement(string key) + => TryGetElementCore(key, null /* owner */); + + public UIElement TryGetElement(string key, UIElement owner) + => TryGetElementCore(key, owner); + + protected virtual void PutElementCore(UIElement element, string key, UIElement owner) + { + var winrtKey = key; + var winrtOwner = owner; + var winrtOwnerAsPanel = EnsureOwnerIsPanelOrNull(winrtOwner); + + ElementInfo elementInfo = new ElementInfo(element, winrtOwnerAsPanel); + + if (m_elements.TryGetValue(winrtKey, out var infos)) + { + infos.Add(elementInfo); + } + else + { + var pool = new List(); + pool.Add(elementInfo); + m_elements[winrtKey] = pool; + } + } + + protected virtual UIElement TryGetElementCore(string key, UIElement owner) + { + if (m_elements.TryGetValue(key, out var elements)) + { + if (elements.Count > 0) + { + ElementInfo elementInfo = default; + + // Prefer an element from the same owner or with no owner so that we don't incur + // the enter/leave cost during recycling. + // TODO: prioritize elements with the same owner to those without an owner. + var winrtOwner = owner; + var iter = elements.FindIndex(elemInfo => elemInfo.Owner == winrtOwner || elemInfo.Owner == null); + + if (iter < 0) + { + iter = elements.Count - 1; + } + + elementInfo = elements[iter]; + elements.RemoveAt(iter); + + var ownerAsPanel = EnsureOwnerIsPanelOrNull(winrtOwner); + if (elementInfo.Owner != null && elementInfo.Owner != ownerAsPanel) + { + // Element is still under its parent. remove it from its parent. + var panel = elementInfo.Owner; + if (panel != null) + { + int childIndex = panel.Children.AsUIElementList().IndexOf(elementInfo.Element); + if (childIndex < 0) + { + throw new InvalidOperationException("ItemsRepeater's child not found in its Children collection."); + } + + panel.Children.RemoveAt(childIndex); + } + } + + return elementInfo.Element; + } + } + + return null; + } + + // UNO: This has been customized as on WinUI the ItemsRecycler is actually a fake Panel + private IPanel EnsureOwnerIsPanelOrNull(UIElement owner) + { + IPanel ownerAsPanel = null; + if (owner != null) + { + ownerAsPanel = owner as IPanel; + if (ownerAsPanel == null) + { + throw new InvalidOperationException("owner must to be a Panel or null."); + } + } + return ownerAsPanel; + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePoolFactory.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePoolFactory.cs new file mode 100644 index 000000000000..648432cb3f2f --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RecyclePoolFactory.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include +#include +#include "ItemsRepeater.common.h" +#include "RecyclePoolFactory.h" +#include "RecyclePool.h" + +GlobalDependencyProperty RecyclePool.s_reuseKeyProperty = null; +GlobalDependencyProperty RecyclePool.s_originTemplateProperty = null; + + +#region IRecyclePoolStatics + +string RecyclePool.GetReuseKey(UIElement const& element) +{ + return auto_unbox(element.GetValue(s_reuseKeyProperty)); +} + +void RecyclePool.SetReuseKey(UIElement const& element, string const& value) +{ + element.SetValue(s_reuseKeyProperty, box_value(value)); +} + +RecyclePool RecyclePool.GetPoolInstance(DataTemplate const& dataTemplate) +{ + if (!s_PoolInstanceProperty) + { + EnsureProperties(); + } + + return RecyclePoolProperties.GetPoolInstance(dataTemplate); +} + +void RecyclePool.SetPoolInstance(DataTemplate const& dataTemplate, RecyclePool const& value) +{ + if (!s_PoolInstanceProperty) + { + EnsureProperties(); + } + + RecyclePoolProperties.SetPoolInstance(dataTemplate, value); +} + + +#endregion + +/* static */ +void RecyclePool.EnsureProperties() +{ + RecyclePoolProperties.EnsureProperties(); + if (s_reuseKeyProperty == null) + { + s_reuseKeyProperty = + InitializeDependencyProperty( + "ReuseKey", + name_of(), + name_of(), + true /* isAttached */, + box_value(wstring_view("")) /* defaultValue */, + null /* propertyChangedCallback */); + } + + if (s_originTemplateProperty == null) + { + s_originTemplateProperty = + InitializeDependencyProperty( + "OriginTemplate", + name_of(), + name_of(), + true /* isAttached */, + null /* defaultValue */, + null /* propertyChangedCallback */); + } +} + +/* static */ +void RecyclePool.ClearProperties() +{ + s_reuseKeyProperty = null; + s_originTemplateProperty = null; + RecyclePoolProperties.ClearProperties(); +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RepeaterLayoutContext.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RepeaterLayoutContext.cs new file mode 100644 index 000000000000..16900a05622f --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/RepeaterLayoutContext.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Windows.Foundation; +using Windows.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls +{ + internal class RepeaterLayoutContext : VirtualizingLayoutContext + { + private readonly WeakReference m_owner; + + public RepeaterLayoutContext(ItemsRepeater owner) + { + m_owner = new WeakReference(owner); + } + + #region ILayoutContext + + protected override int ItemCountCore => GetOwner().ItemsSourceView?.Count ?? 0; + + protected override UIElement GetOrCreateElementAtCore(int index, ElementRealizationOptions options) + { + return GetOwner().GetElementImpl(index, + (options & ElementRealizationOptions.ForceCreate) == ElementRealizationOptions.ForceCreate, + (options & ElementRealizationOptions.SuppressAutoRecycle) == ElementRealizationOptions.SuppressAutoRecycle); + } + + protected override object LayoutStateCore + { + get => GetOwner().LayoutState; + set => GetOwner().LayoutState = value; + } + + protected override object GetItemAtCore(int index) + => GetOwner().ItemsSourceView.GetAt(index); + + protected override void RecycleElementCore(UIElement element) + { + var owner = GetOwner(); + // REPEATER_TRACE_INFO("RepeaterLayout - RecycleElement: %d \n", owner.GetElementIndex(element)); + owner.ClearElementImpl(element); + } + + protected override Rect RealizationRectCore => GetOwner().RealizationWindow; + + protected override int RecommendedAnchorIndexCore + { + get + { + int anchorIndex = -1; + var repeater = GetOwner(); + var anchor = repeater.SuggestedAnchor; + if (anchor != null) + { + anchorIndex = repeater.GetElementIndex(anchor); + } + + return anchorIndex; + } + } + + protected override Point LayoutOriginCore + { + get => GetOwner().LayoutOrigin; + set => GetOwner().LayoutOrigin = value; + } + + #endregion + + ItemsRepeater GetOwner() + => m_owner.TryGetTarget(out var owner) + ? owner + : throw new InvalidOperationException("Owning ItemsRepeater has been collected"); + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/UniqueIdElementPool.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/UniqueIdElementPool.cs new file mode 100644 index 000000000000..ed8a571d0c79 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/UniqueIdElementPool.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using Windows.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls +{ + internal class UniqueIdElementPool + { + private Dictionary m_elementMap = new Dictionary(); + private readonly ItemsRepeater m_owner; + + public UniqueIdElementPool(ItemsRepeater owner) + { + // ItemsRepeater is not fully constructed yet. Don't interact with it. + + m_owner = owner; + } + + public void Add(UIElement element) + { + global::System.Diagnostics.Debug.Assert(m_owner.ItemsSourceView.HasKeyIndexMapping); + + var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); + var key = virtInfo.UniqueId; + + if (m_elementMap.ContainsKey(key)) + { + throw new InvalidOperationException($"The unique id provided ({virtInfo.UniqueId}) is not unique."); + } + + m_elementMap.Add(key, element); + } + + public UIElement Remove(int index) + { + global::System.Diagnostics.Debug.Assert(m_owner.ItemsSourceView.HasKeyIndexMapping); + + // Check if there is already a element in the mapping and if so, use it. + UIElement element = null; + string key = m_owner.ItemsSourceView.KeyFromIndex(index); + if (m_elementMap.TryGetValue(key, out element)) + { + m_elementMap.Remove(key); + } + + return element; + } + + public void Clear() + { + global::System.Diagnostics.Debug.Assert(m_owner.ItemsSourceView.HasKeyIndexMapping); + m_elementMap.Clear(); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ViewManager.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ViewManager.cs index 509219570f61..f01d5f602da5 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ViewManager.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/ViewManager.cs @@ -7,11 +7,33 @@ using System.Diagnostics; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Markup; namespace Microsoft.UI.Xaml.Controls { + internal struct PinnedElementInfo + { + private UIElement m_pinnedElement; + + // We hold on VirtualizationInfo to make sure we can + // quickly access its content rather than go through + // ItemsRepeater.GetVirtualizationInfo(element) which is + // slower (assuming it's implemented using attached + // properties). + private VirtualizationInfo m_virtInfo; + + + public PinnedElementInfo(ITrackerHandleManager owner, UIElement element) + { + + } + + public UIElement PinnedElement => m_pinnedElement; + public VirtualizationInfo VirtualizationInfo => m_virtInfo; + }; + internal class ViewManager { private ItemsRepeater m_owner; @@ -44,22 +66,22 @@ internal class ViewManager const int FirstRealizedElementIndexDefault = int.MaxValue; const int LastRealizedElementIndexDefault = int.MinValue; - public ViewManager(ItemsRepeater* owner) + public ViewManager(ItemsRepeater owner) { // ItemsRepeater is not fully constructed yet. Don't interact with it. m_owner = owner; - m_resetPool(owner); + m_resetPool = owner; - //m_lastFocusedElement(owner); - //m_phaser(owner); - //m_ElementFactoryGetArgs(owner); - //m_ElementFactoryRecycleArgs(owner); + m_lastFocusedElement(owner); + m_phaser(owner); + m_ElementFactoryGetArgs(owner); + m_ElementFactoryRecycleArgs(owner); } [Conditional("TRACE")] - private static void REPEATER_TRACE_INFO(string text) - => Console.WriteLine(text); + private static void REPEATER_TRACE_INFO(string text, params object[] parameters) + => Console.WriteLine(text, parameters); [Conditional("TRACE")] private static void REPEATER_TRACE_PERF(string text) @@ -76,11 +98,11 @@ public UIElement GetElement(int index, bool forceCreate, bool suppressAutoRecycl { // check if this is the anchor made through repeater in preparation // for a bring into view. - var madeAnchor = m_owner.MadeAnchor(); + var madeAnchor = m_owner.MadeAnchor; if (madeAnchor != null) { var anchorVirtInfo = ItemsRepeater.TryGetVirtualizationInfo(madeAnchor); - if (anchorVirtInfo.Index() == index) + if (anchorVirtInfo.Index == index) { element = madeAnchor; } @@ -96,14 +118,14 @@ public UIElement GetElement(int index, bool forceCreate, bool suppressAutoRecycl var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); if (suppressAutoRecycle) { - virtInfo.AutoRecycleCandidate(false); - REPEATER_TRACE_INFO("%* GetElement: %d Not AutoRecycleCandidate: \n", m_owner.Indent(), virtInfo.Index()); + virtInfo.AutoRecycleCandidate = false; + REPEATER_TRACE_INFO("%* GetElement: %d Not AutoRecycleCandidate: \n", m_owner.Indent(), virtInfo.Index); } else { - virtInfo.AutoRecycleCandidate(true); - virtInfo.KeepAlive(true); - REPEATER_TRACE_INFO("%* GetElement: %d AutoRecycleCandidate: \n", m_owner.Indent(), virtInfo.Index()); + virtInfo.AutoRecycleCandidate = true; + virtInfo.KeepAlive = true; + REPEATER_TRACE_INFO("%* GetElement: %d AutoRecycleCandidate: \n", m_owner.Indent(), virtInfo.Index); } return element; @@ -112,7 +134,7 @@ public UIElement GetElement(int index, bool forceCreate, bool suppressAutoRecycl public void ClearElement(UIElement element, bool isClearedDueToCollectionChange) { var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - int index = virtInfo.Index(); + int index = virtInfo.Index; bool cleared = ClearElementToUniqueIdResetPool(element, virtInfo) || ClearElementToAnimator(element, virtInfo) || @@ -150,7 +172,7 @@ public void ClearElement(UIElement element, bool isClearedDueToCollectionChange) } } - void ClearElementToElementFactory(UIElement element) + public void ClearElementToElementFactory(UIElement element) { m_owner.OnElementClearing(element); @@ -193,7 +215,7 @@ void ClearElementToElementFactory(UIElement element) // Focused element is going away. Remove the tracked last focused element // and pick a reasonable next focus if we can find one within the layout // realized elements. - int clearedIndex = virtInfo.Index(); + int clearedIndex = virtInfo.Index; MoveFocusFromClearedIndex(clearedIndex); } @@ -236,13 +258,13 @@ Control FindFocusCandidate(int clearedIndex, UIElement focusedChild) UIElement nextElement = null; UIElement previousElement = null; var children = m_owner.Children; - for (uint i = 0u; i < children.Count; ++i) + for (int i = 0; i < children.Count; ++i) { - var child = children.GetAt(i); + var child = children[i]; var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(child); - if (virtInfo && virtInfo.IsHeldByLayout()) + if (virtInfo && virtInfo.IsHeldByLayout) { - int currentIndex = virtInfo.Index(); + int currentIndex = virtInfo.Index; if (currentIndex < clearedIndex) { if (currentIndex > previousIndex) @@ -319,7 +341,7 @@ public void PrunePinnedElements() for (int i = 0; i < m_pinnedPool.Count; ++i) { var elementInfo = m_pinnedPool[i]; - var virtInfo = elementInfo.VirtualizationInfo(); + var virtInfo = elementInfo.VirtualizationInfo; MUX_ASSERT(virtInfo.Owner == ElementOwner.PinnedPool); @@ -329,7 +351,7 @@ public void PrunePinnedElements() --i; // Pinning was the only thing keeping this element alive. - ClearElementToElementFactory(elementInfo.PinnedElement()); + ClearElementToElementFactory(elementInfo.PinnedElement); } } } @@ -350,7 +372,7 @@ public void UpdatePin(UIElement element, bool addPin) { virtInfo.AddPin(); } - else if (virtInfo.IsPinned()) + else if (virtInfo.IsPinned) { if (virtInfo.RemovePin() == 0) { @@ -385,11 +407,11 @@ public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args var childCount = children.Count; for (uint i = 0u; i < childCount; ++i) { - var element = children.GetAt(i); + var element = children[i]; var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - var dataIndex = virtInfo.Index(); + var dataIndex = virtInfo.Index; - if (virtInfo.IsRealized() && dataIndex >= newIndex) + if (virtInfo.IsRealized && dataIndex >= newIndex) { UpdateElementIndex(element, virtInfo, dataIndex + newCount); } @@ -402,12 +424,12 @@ public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args for (int i = 0; i < m_pinnedPool.Count; ++i) { var elementInfo = m_pinnedPool[i]; - var virtInfo = elementInfo.VirtualizationInfo(); - var dataIndex = virtInfo.Index(); + var virtInfo = elementInfo.VirtualizationInfo; + var dataIndex = virtInfo.Index; - if (virtInfo.IsRealized() && dataIndex >= newIndex) + if (virtInfo.IsRealized && dataIndex >= newIndex) { - var element = elementInfo.PinnedElement(); + var element = elementInfo.PinnedElement; UpdateElementIndex(element, virtInfo, dataIndex + newCount); } } @@ -450,13 +472,13 @@ public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args // countChange > 0 : countChange items were added // countChange < 0 : -countChange items were removed var children = m_owner.Children; - for (uint i = 0u; i < children.Count; ++i) + for (int i = 0; i < children.Count; ++i) { - var element = children.GetAt(i); + var element = children[i]; var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - var dataIndex = virtInfo.Index(); + var dataIndex = virtInfo.Index; - if (virtInfo.IsRealized()) + if (virtInfo.IsRealized) { if (dataIndex >= oldStartIndex + oldCount) { @@ -477,15 +499,15 @@ public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args var oldStartIndex = args.OldStartingIndex; var oldCount = (int)(args.OldItems.Count); var children = m_owner.Children; - for (uint i = 0u; i < children.Count; ++i) + for (int i = 0; i < children.Count; ++i) { - var element = children.GetAt(i); + var element = children[i]; var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - var dataIndex = virtInfo.Index(); + var dataIndex = virtInfo.Index; - if (virtInfo.IsRealized()) + if (virtInfo.IsRealized) { - if (virtInfo.AutoRecycleCandidate() && oldStartIndex <= dataIndex && dataIndex < oldStartIndex + oldCount) + if (virtInfo.AutoRecycleCandidate && oldStartIndex <= dataIndex && dataIndex < oldStartIndex + oldCount) { // If we are doing the mapping, remove the element who's data was removed. m_owner.ClearElementImpl(element); @@ -509,7 +531,7 @@ public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args // There should be no elements in the reset pool at this time. MUX_ASSERT(m_resetPool.IsEmpty()); - if (m_owner.ItemsSourceView.HasKeyIndexMapping()) + if (m_owner.ItemsSourceView.HasKeyIndexMapping) { m_isDataSourceStableResetPending = true; } @@ -517,11 +539,11 @@ public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args // Walk through all the elements and make sure they are cleared, they will go into // the stable id reset pool. var children = m_owner.Children; - for (uint i = 0u; i < children.Count; ++i) + for (int i = 0; i < children.Count; ++i) { - var element = children.GetAt(i); + var element = children[i]; var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - if (virtInfo.IsRealized() && virtInfo.AutoRecycleCandidate()) + if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate) { m_owner.ClearElementImpl(element); } @@ -545,7 +567,7 @@ void EnsureFirstLastRealizedIndices() public void OnLayoutChanging() { - if (m_owner.ItemsSourceView && + if (m_owner.ItemsSourceView != null && m_owner.ItemsSourceView.HasKeyIndexMapping) { m_isDataSourceStableResetPending = true; @@ -596,17 +618,17 @@ UIElement GetElementIfAlreadyHeldByLayout(int index) (m_firstRealizedElementIndexHeldByLayout != FirstRealizedElementIndexDefault && m_lastRealizedElementIndexHeldByLayout != LastRealizedElementIndexDefault)); var children = m_owner.Children; - for (uint i = 0u; i < children.Count; ++i) + for (int i = 0; i < children.Count; ++i) { - var child = children.GetAt(i); + var child = children[i]; var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(child); - if (virtInfo && virtInfo.IsHeldByLayout()) + if (virtInfo && virtInfo.IsHeldByLayout) { // Only give back elements held by layout. If someone else is holding it, they will be served by other methods. - int childIndex = virtInfo.Index(); + int childIndex = virtInfo.Index; m_firstRealizedElementIndexHeldByLayout = Math.Min(m_firstRealizedElementIndexHeldByLayout, childIndex); m_lastRealizedElementIndexHeldByLayout = Math.Max(m_lastRealizedElementIndexHeldByLayout, childIndex); - if (virtInfo.Index() == index) + if (virtInfo.Index == index) { element = child; // If we have valid first/last indices, we don't have to walk the rest, but if we @@ -655,13 +677,13 @@ UIElement GetElementFromPinnedElements(int index) for (int i = 0; i < m_pinnedPool.Count; ++i) { var elementInfo = m_pinnedPool[i]; - var virtInfo = elementInfo.VirtualizationInfo(); + var virtInfo = elementInfo.VirtualizationInfo; - if (virtInfo.Index() == index) + if (virtInfo.Index == index) { m_pinnedPool.RemoveAt(i); - element = elementInfo.PinnedElement(); - elementInfo.VirtualizationInfo().MoveOwnershipToLayoutFromPinnedPool(); + element = elementInfo.PinnedElement; + elementInfo.VirtualizationInfo.MoveOwnershipToLayoutFromPinnedPool(); // Update realized indices m_firstRealizedElementIndexHeldByLayout = Math.Min(m_firstRealizedElementIndexHeldByLayout, index); @@ -704,21 +726,26 @@ UIElement GetElementFromElementFactory(int index) } } - var elementFactory = providedElementFactory; + var elementFactory = () => providedElementFactory; { - if (!providedElementFactory) + if (providedElementFactory == null) { // If no ItemTemplate was provided, use a default - (var factory = XamlReader.Load("") as DataTemplate); - m_owner.ItemTemplate(factory); - return m_owner.ItemTemplateShim(); + //var factory = XamlReader.Load("") as DataTemplate; + var factory = new DataTemplate(() => + { + var tb = new TextBlock(); + tb.SetBinding(TextBlock.TextProperty, new Binding()); + return tb; + }); + m_owner.ItemTemplate = factory; + return m_owner.ItemTemplateShim; } return providedElementFactory; } - (); - var args = [this]() + var args = [this]() { if (!m_ElementFactoryGetArgs) { @@ -736,7 +763,7 @@ UIElement GetElementFromElementFactory(int index) }); args.Data(data); - args.Parent(*m_owner); + args.Parent(m_owner); (args as ElementFactoryGetArgs).Index(index); return elementFactory.GetElement(args); @@ -856,7 +883,7 @@ bool ClearElementToAnimator(UIElement element, VirtualizationInfo virtInfo) bool cleared = m_owner.AnimationManager.ClearElement(element); if (cleared) { - int clearedIndex = virtInfo.Index(); + int clearedIndex = virtInfo.Index; virtInfo.MoveOwnershipToAnimator(); if (m_lastFocusedElement == element) { @@ -878,10 +905,10 @@ bool ClearElementToPinnedPool(UIElement element, VirtualizationInfo virtInfo, bo if (moveToPinnedPool) { -#ifdef _DEBUG +#if DEBUG for (int i = 0; i < m_pinnedPool.Count; ++i) { - MUX_ASSERT(m_pinnedPool[i].PinnedElement() != element); + MUX_ASSERT(m_pinnedPool[i].PinnedElement != element); } #endif @@ -910,7 +937,7 @@ void UpdateFocusedElement() if (parent is ItemsRepeater repeater) { var element = child as UIElement; - if (repeater == owner && ItemsRepeater.GetVirtualizationInfo(element).IsRealized()) + if (repeater == owner && ItemsRepeater.GetVirtualizationInfo(element).IsRealized) { focusedElement = element; } @@ -927,12 +954,12 @@ void UpdateFocusedElement() // we need to unpin the old one and pin the new one. if (m_lastFocusedElement != focusedElement) { - if (m_lastFocusedElement) + if (m_lastFocusedElement != null) { UpdatePin(m_lastFocusedElement, false /* addPin */); } - if (focusedElement) + if (focusedElement != null) { UpdatePin(focusedElement, true /* addPin */); } diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.ChildrenCollection.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.ChildrenCollection.cs new file mode 100644 index 000000000000..1ebd4386cfbf --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.ChildrenCollection.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls +{ + partial class VirtualLayoutContextAdapter + { + private class ChildrenCollection : IReadOnlyList, IEnumerable + { + private VirtualizingLayoutContext m_context; + + public ChildrenCollection(VirtualizingLayoutContext context) + { + m_context = context; + } + + public int Count => m_context.ItemCount; + + public UIElement this[int index] => m_context.GetOrCreateElementAt(index, ElementRealizationOptions.None); + + public IEnumerator GetEnumerator() + => new Iterator(this); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + } + + private class Iterator : IEnumerator + { + private readonly IReadOnlyList m_childCollection; + private int m_currentIndex = 0; + + public Iterator(IReadOnlyList childCollection) + { + m_childCollection = childCollection; + } + + object IEnumerator.Current => Current; + public UIElement Current + { + get + { + if (m_currentIndex < m_childCollection.Count) + { + return m_childCollection[m_currentIndex]; + } + else + { + throw new IndexOutOfRangeException(); + } + } + } + + public bool MoveNext() + { + if (m_currentIndex < m_childCollection.Count) + { + ++m_currentIndex; + return m_currentIndex < m_childCollection.Count; + } + else + { + throw new IndexOutOfRangeException(); + } + } + + public void Reset() { } + + public void Dispose() { } + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.cs new file mode 100644 index 000000000000..bc7e89a0aa88 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualLayoutContextAdapter.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using Windows.UI.Xaml; +using Uno.Extensions; + +namespace Microsoft.UI.Xaml.Controls +{ + internal partial class VirtualLayoutContextAdapter : NonVirtualizingLayoutContext + { + private readonly WeakReference m_virtualizingContext; + + private IReadOnlyList m_children; + + public VirtualLayoutContextAdapter(VirtualizingLayoutContext virtualizingContext) + { + m_virtualizingContext = new WeakReference(virtualizingContext); + } + + #region ILayoutContextOverrides + + protected override object LayoutStateCore + { + get => m_virtualizingContext.TryGetTarget(out var context) ? context.LayoutState : null; + set + { + if (m_virtualizingContext.TryGetTarget(out var context)) + { + context.LayoutState = value; + } + } + } + + #endregion + + #region INonVirtualizingLayoutContextOverrides + + protected override IReadOnlyList ChildrenCore + { + get + { + if (m_children == null) + { + m_children = new ChildrenCollection(m_virtualizingContext.GetTarget()); + } + + return m_children; + } + } + + #endregion + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizationInfo.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizationInfo.cs index 3b3561ee401a..adbd284fcb03 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizationInfo.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizationInfo.cs @@ -5,13 +5,14 @@ using Windows.Foundation; using Windows.UI.Xaml.Markup; using Android.OS; +using Uno.Extensions; namespace Microsoft.UI.Xaml.Controls { internal class VirtualizationInfo { - private const int PhaseNotSpecified = int.MinValue; - private const int PhaseReachedEnd = -1; + public const int PhaseNotSpecified = int.MinValue; + public const int PhaseReachedEnd = -1; private uint m_pinCounter ; private int m_index = -1; @@ -36,7 +37,7 @@ public Rect ArrangeBounds set => m_arrangeBounds = value; } - string UniqueId => m_uniqueId; + public string UniqueId => m_uniqueId; public bool KeepAlive { @@ -73,6 +74,10 @@ public int Phase set => m_phase = value; } + public object Data => m_data; + + public IDataTemplateComponent DataTemplateComponent => m_dataTemplateComponent.GetTarget(); + public void UpdatePhasingInfo(int phase, object data, IDataTemplateComponent component) { m_phase = phase; @@ -91,7 +96,7 @@ private void MoveOwnershipToLayoutFromElementFactory(int index, string uniqueId) m_uniqueId = uniqueId; } - private void MoveOwnershipToLayoutFromUniqueIdResetPool() + public void MoveOwnershipToLayoutFromUniqueIdResetPool() { global::System.Diagnostics.Debug.Assert(m_owner == ElementOwner.UniqueIdResetPool); @@ -149,7 +154,7 @@ void MoveOwnershipToPinnedPool() #endregion - uint AddPin() + public uint AddPin() { if (!IsRealized) @@ -160,7 +165,7 @@ uint AddPin() return ++m_pinCounter; } - uint RemovePin() + public uint RemovePin() { if (!IsRealized) diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizingLayoutContext.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizingLayoutContext.cs index 3a8a8b080d29..bcd3a4417b1f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizingLayoutContext.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Repeater/VirtualizingLayoutContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.UI.Xaml.Controls public class VirtualizingLayoutContext : LayoutContext { #region IVirtualizingLayoutContext - public int ItemCount => ItemCountCore(); + public int ItemCount => ItemCountCore; public Point LayoutOrigin { @@ -18,7 +18,7 @@ public Point LayoutOrigin set => LayoutOriginCore = value; } - public Rect RealizationRect => RealizationRectCore(); + public Rect RealizationRect => RealizationRectCore; public int RecommendedAnchorIndex => RecommendedAnchorIndexCore; @@ -54,18 +54,16 @@ protected virtual UIElement GetOrCreateElementAtCore(int index, ElementRealizati protected virtual void RecycleElementCore(UIElement element) => throw new NotImplementedException(); - protected virtual Rect RealizationRectCore() - => throw new NotImplementedException(); + protected virtual Rect RealizationRectCore => throw new NotImplementedException(); - protected virtual int ItemCountCore() - => throw new NotImplementedException(); + protected virtual int ItemCountCore => throw new NotImplementedException(); #endregion internal NonVirtualizingLayoutContext GetNonVirtualizingContextAdapter() { if (!m_contextAdapter) { - (m_contextAdapter = new VirtualLayoutContextAdapter(get_strong() as VirtualizingLayoutContext)); + m_contextAdapter = new VirtualLayoutContextAdapter(this); } return m_contextAdapter; diff --git a/src/Uno.UI/UI/Xaml/DataTemplateSelector.cs b/src/Uno.UI/UI/Xaml/DataTemplateSelector.cs index 5c9da9388faa..d76bcd82e86a 100644 --- a/src/Uno.UI/UI/Xaml/DataTemplateSelector.cs +++ b/src/Uno.UI/UI/Xaml/DataTemplateSelector.cs @@ -13,22 +13,22 @@ void Initialize () { } - public DataTemplate SelectTemplate (object item) + public DataTemplate SelectTemplate(object item) { return SelectTemplateCore(item); } - public DataTemplate SelectTemplate (object item, DependencyObject container) + public DataTemplate SelectTemplate(object item, DependencyObject container) { return SelectTemplateCore(item, container); } - protected virtual DataTemplate SelectTemplateCore (object item) + protected virtual DataTemplate SelectTemplateCore(object item) { return null; } - protected virtual DataTemplate SelectTemplateCore (object item, DependencyObject container) + protected virtual DataTemplate SelectTemplateCore(object item, DependencyObject container) { return null; } diff --git a/src/Uno.UI/UI/Xaml/UIElementCollection.Android.cs b/src/Uno.UI/UI/Xaml/UIElementCollection.Android.cs index 67ef7edeb103..f9e8bd5e8848 100644 --- a/src/Uno.UI/UI/Xaml/UIElementCollection.Android.cs +++ b/src/Uno.UI/UI/Xaml/UIElementCollection.Android.cs @@ -4,6 +4,7 @@ using Uno.Extensions; using Android.Views; using System; +using Microsoft.UI.Xaml.Controls; using Uno.UI.Controls; using Uno.UI; @@ -100,3 +101,4 @@ private void MoveCore(uint oldIndex, uint newIndex) } +#endif \ No newline at end of file