/// <summary> /// Get the index for the next item after <paramref name="currentItem"/>. /// </summary> internal Uno.UI.IndexPath?GetNextItemIndex(Uno.UI.IndexPath? currentItem, int direction) { if (!HasItems) { return(null); } if (currentItem == null) { // Null is treated as 'just before the first item.' if (direction == 1) { var firstNonEmptySection = IsGrouping ? GetNextNonEmptySection(-1, 1).Value : 0; return(Uno.UI.IndexPath.FromRowSection(0, firstNonEmptySection)); } else { return(null); } } if (direction == 1) { return(GetIncrementedItemIndex(currentItem.Value)); } if (direction == -1) { return(GetDecrementedItemIndex(currentItem.Value)); } throw new ArgumentOutOfRangeException(nameof(direction)); }
/// <summary> /// Sequentially applies offsets to a collection index resulting from multiple collection operations. /// </summary> /// <param name="index">The index in the collection prior to the operation</param> /// <param name="collectionChanges">The changes to be applied, in order from oldest to newest.</param> /// <returns>The offset position, or null if this position is no longer valid (ie because it has been removed by one of the operations).</returns> public static Uno.UI.IndexPath?Offset(Uno.UI.IndexPath index, IEnumerable <CollectionChangedOperation> collectionChanges) { Uno.UI.IndexPath?newIndex = index; foreach (var change in collectionChanges) { if (newIndex is Uno.UI.IndexPath newIndexValue) { newIndex = change.Offset(newIndexValue); } else { break; } } return(newIndex); }
protected override Uno.UI.IndexPath?GetDynamicSeedIndex(Uno.UI.IndexPath? firstVisibleItem, int availableBreadth) { //Get the first preceding item that is at the end of a line var currentItem = firstVisibleItem; var itemsPerLine = ResolveMaximumItemsInLine(availableBreadth); while (currentItem != null) { currentItem = GetNextUnmaterializedItem(GeneratorDirection.Backward, currentItem); if (currentItem?.Section != firstVisibleItem?.Section) { return(currentItem); } if ((currentItem?.Row + 1) % itemsPerLine == 0) { return(currentItem); } } return(null); }
/// <summary> /// Get the index of the next item that has not yet been materialized in the nominated fill direction. Returns null if there are no more available items in the source. /// </summary> protected Uno.UI.IndexPath?GetNextUnmaterializedItem(GeneratorDirection fillDirection, Uno.UI.IndexPath? currentMaterializedItem) { return(XamlParent?.GetNextItemIndex(currentMaterializedItem, fillDirection == GeneratorDirection.Forward ? 1 : -1)); }
protected override Line CreateLine(GeneratorDirection direction, int extentOffset, int breadthOffset, int availableBreadth, RecyclerView.Recycler recycler, RecyclerView.State state, Uno.UI.IndexPath nextVisibleItem, bool isNewGroup ) { var itemsInLine = ResolveMaximumItemsInLine(availableBreadth); var firstItemInLine = nextVisibleItem; //Find first item in line, since the item we are passed is the last if (direction == GeneratorDirection.Backward) { // We are recreating the last line of the group - it may be truncated (if the total items are not an even multiple // of the items-per-line). if (isNewGroup) { itemsInLine = XamlParent.GetItemsOnLastLine(firstItemInLine.Section, itemsInLine); } for (int i = 0; i < itemsInLine - 1; i++) { firstItemInLine = GetNextUnmaterializedItem(GeneratorDirection.Backward, firstItemInLine).Value; var isCorrectGroup = firstItemInLine.Section == nextVisibleItem.Section; if (!isCorrectGroup) { //TODO: fix bug that makes this happen (#47229) } Debug.Assert(isCorrectGroup, GetAssertMessage("First item should not be from a different group")); } } Uno.UI.IndexPath lastItemInLine = firstItemInLine; Uno.UI.IndexPath?currentItem = firstItemInLine; var availableWidth = ResolveAvailableWidth(availableBreadth); var availableHeight = ResolveAvailableHeight(availableBreadth); int usedBreadth = 0; for (int i = 0; i < itemsInLine; i++) { var view = recycler.GetViewForPosition(GetFlatItemIndex(currentItem.Value), state); if (!(view is SelectorItem)) { throw new InvalidOperationException($"Expected {nameof(SelectorItem)} but received {view?.GetType().ToString() ?? "<null>"}"); } //Add view before we measure it, this ensures that DP inheritances are correctly applied AddView(view, direction); var slotSize = new Windows.Foundation.Size(availableWidth, availableHeight).PhysicalToLogicalPixels(); var measuredSize = _layouter.MeasureChild(view, slotSize); var physicalMeasuredSize = measuredSize.LogicalToPhysicalPixels(); var measuredWidth = (int)physicalMeasuredSize.Width; var measuredHeight = (int)physicalMeasuredSize.Height; if (_implicitItemWidth == null) { //Set these values to dimensions of first materialised item _implicitItemWidth = measuredWidth; _implicitItemHeight = measuredHeight; // When an item dimension is not fixed, we need to arrange based on the measured size, // otherwise the arrange will be passed a dimension that is too large and the first // few items will not be visible if (double.IsNaN(ItemWidth)) { slotSize.Width = ViewHelper.PhysicalToLogicalPixels(_implicitItemWidth.Value); } if (double.IsNaN(ItemHeight)) { slotSize.Height = ViewHelper.PhysicalToLogicalPixels(_implicitItemHeight.Value); } availableWidth = ResolveAvailableWidth(availableBreadth); availableHeight = ResolveAvailableHeight(availableBreadth); itemsInLine = ResolveMaximumItemsInLine(availableBreadth); } LayoutChild(view, GeneratorDirection.Forward, //We always lay out view 'top down' so that it is aligned correctly if its height is less than the line height direction == GeneratorDirection.Forward ? extentOffset : extentOffset - ResolveItemExtent().Value, breadthOffset + usedBreadth, slotSize ); usedBreadth += ResolveItemBreadth().Value; lastItemInLine = currentItem.Value; currentItem = GetNextUnmaterializedItem(GeneratorDirection.Forward, currentItem); if (currentItem == null || currentItem.Value.Section != firstItemInLine.Section) { itemsInLine = i + 1; break; } } return(new Line { NumberOfViews = itemsInLine, Extent = ResolveItemExtent().Value, Breadth = usedBreadth, FirstItem = firstItemInLine, LastItem = lastItemInLine }); }