public ILayoutable GetAt(int realizedIndex) { ILayoutable element; if (IsVirtualizingContext) { if (_realizedElements[realizedIndex] == null) { // Sentinel. Create the element now since we need it. int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "Creating element for sentinal with data index {Index}", dataIndex); element = _context.GetOrCreateElementAt( dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); _realizedElements[realizedIndex] = element; } else { element = _realizedElements[realizedIndex]; } } else { // realizedIndex and dataIndex are the same (everything is realized) element = _context.GetOrCreateElementAt( realizedIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); } return(element); }
public ILayoutable GetAt(int realizedIndex) { ILayoutable element; if (IsVirtualizingContext) { if (_realizedElements[realizedIndex] == null) { // Sentinel. Create the element now since we need it. int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex); element = _context.GetOrCreateElementAt( dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); _realizedElements[realizedIndex] = element; } else { element = _realizedElements[realizedIndex]; } } else { // realizedIndex and dataIndex are the same (everything is realized) element = _context.GetOrCreateElementAt( realizedIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); } return(element); }
internal void EnsureElementSize( Size availableSize, VirtualizingLayoutContext context, double layoutItemWidth, double layoutItemHeight, UniformGridLayoutItemsStretch stretch, Orientation orientation, double minRowSpacing, double minColumnSpacing, int maxItemsPerLine) { if (maxItemsPerLine == 0) { maxItemsPerLine = 1; } if (context.ItemCount > 0) { // If the first element is realized we don't need to cache it or to get it from the context var realizedElement = FlowAlgorithm.GetElementIfRealized(0); if (realizedElement != null) { realizedElement.Measure(availableSize); SetSize(realizedElement, layoutItemWidth, layoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); _cachedFirstElement = null; } else { if (_cachedFirstElement == null) { // we only cache if we aren't realizing it _cachedFirstElement = context.GetOrCreateElementAt( 0, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); // expensive } _cachedFirstElement.Measure(availableSize); SetSize(_cachedFirstElement, layoutItemWidth, layoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); // See if we can move ownership to the flow algorithm. If we can, we do not need a local cache. bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement); if (added) { _cachedFirstElement = null; } } } }
internal void EnsureElementSize( Size availableSize, VirtualizingLayoutContext context, double layoutItemWidth, double LayoutItemHeight, UniformGridLayoutItemsStretch stretch, Orientation orientation, double minRowSpacing, double minColumnSpacing) { if (context.ItemCount > 0) { // If the first element is realized we don't need to cache it or to get it from the context var realizedElement = FlowAlgorithm.GetElementIfRealized(0); if (realizedElement != null) { realizedElement.Measure(availableSize); SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing); _cachedFirstElement = null; } else { if (_cachedFirstElement == null) { // we only cache if we aren't realizing it _cachedFirstElement = context.GetOrCreateElementAt( 0, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); // expensive } _cachedFirstElement.Measure(availableSize); // This doesn't need to be done in the UWP version and I'm not sure why. If we // don't do this here, and we receive a recycled element then it will be shown // at its previous arrange point, but we don't want it shown at all until its // arranged. _cachedFirstElement.Arrange(new Rect(-10000.0, -10000.0, 0, 0)); SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing); // See if we can move ownership to the flow algorithm. If we can, we do not need a local cache. bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement); if (added) { _cachedFirstElement = null; } } } }
private void MakeAnchor( VirtualizingLayoutContext context, int index, Size availableSize) { _elementManager.ClearRealizedRange(); // FlowLayout requires that the anchor is the first element in the row. var internalAnchor = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement(index, availableSize, context); // No need to set the position of the anchor. // (0,0) is fine for now since the extent can // grow in any direction. for (int dataIndex = internalAnchor.Index; dataIndex < index + 1; ++dataIndex) { var element = context.GetOrCreateElementAt(dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); element.Measure(_algorithmCallbacks.Algorithm_GetMeasureSize(dataIndex, availableSize, context)); _elementManager.Add(element, dataIndex); } }
private int GetAnchorIndex( Size availableSize, bool isWrapping, double minItemSpacing, string layoutId) { int anchorIndex = -1; var anchorPosition = new Point(); var context = _context; if (!IsVirtualizingContext) { // Non virtualizing host, start generating from the element 0 anchorIndex = context.ItemCount > 0 ? 0 : -1; } else { bool isRealizationWindowConnected = _elementManager.IsWindowConnected(RealizationRect, _orientation.ScrollOrientation, _scrollOrientationSameAsFlow); // Item spacing and size in non-virtualizing direction change can cause elements to reflow // and get a new column position. In that case we need the anchor to be positioned in the // correct column. bool needAnchorColumnRevaluation = isWrapping && ( _orientation.Minor(_lastAvailableSize) != _orientation.Minor(availableSize) || _lastItemSpacing != minItemSpacing || _collectionChangePending); var suggestedAnchorIndex = _context.RecommendedAnchorIndex; var isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && _elementManager.IsDataIndexRealized(suggestedAnchorIndex); if (isAnchorSuggestionValid) { anchorIndex = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement( suggestedAnchorIndex, availableSize, context).Index; if (_elementManager.IsDataIndexRealized(anchorIndex)) { var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex); if (needAnchorColumnRevaluation) { // We were provided a valid anchor, but its position might be incorrect because for example it is in // the wrong column. We do know that the anchor is the first element in the row, so we can force the minor position // to start at 0. anchorPosition = _orientation.MinorMajorPoint(0, _orientation.MajorStart(anchorBounds)); } else { anchorPosition = new Point(anchorBounds.X, anchorBounds.Y); } } else { // It is possible to end up in a situation during a collection change where GetAnchorForTargetElement returns an index // which is not in the realized range. Eg. insert one item at index 0 for a grid layout. // SuggestedAnchor will be 1 (used to be 0) and GetAnchorForTargetElement will return 0 (left most item in row). However 0 is not in the // realized range yet. In this case we realize the gap between the target anchor and the suggested anchor. int firstRealizedDataIndex = _elementManager.GetDataIndexFromRealizedRangeIndex(0); for (int i = firstRealizedDataIndex - 1; i >= anchorIndex; --i) { _elementManager.EnsureElementRealized(false /*forward*/, i, layoutId); } var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(suggestedAnchorIndex); anchorPosition = _orientation.MinorMajorPoint(0, _orientation.MajorStart(anchorBounds)); } } else if (needAnchorColumnRevaluation || !isRealizationWindowConnected) { // The anchor is based on the realization window because a connected ItemsRepeater might intersect the realization window // but not the visible window. In that situation, we still need to produce a valid anchor. var anchorInfo = _algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, context); anchorIndex = anchorInfo.Index; anchorPosition = _orientation.MinorMajorPoint(0, anchorInfo.Offset); } else { // No suggestion - just pick first in realized range anchorIndex = _elementManager.GetDataIndexFromRealizedRangeIndex(0); var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0); anchorPosition = new Point(firstElementBounds.X, firstElementBounds.Y); } } _firstRealizedDataIndexInsideRealizationWindow = _lastRealizedDataIndexInsideRealizationWindow = anchorIndex; if (_elementManager.IsIndexValidInData(anchorIndex)) { if (!_elementManager.IsDataIndexRealized(anchorIndex)) { // Disconnected, throw everything and create new anchor _elementManager.ClearRealizedRange(); var anchor = _context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); _elementManager.Add(anchor, anchorIndex); } var anchorElement = _elementManager.GetRealizedElement(anchorIndex); var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, _context); var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height); _elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds); } else { _elementManager.ClearRealizedRange(); } // TODO: Perhaps we can track changes in the property setter _lastAvailableSize = availableSize; _lastItemSpacing = minItemSpacing; return(anchorIndex); }
internal void RecycleElementAt(int index) { var element = _context.GetOrCreateElementAt(index); _context.RecycleElement(element); }
/// <inheritdoc /> protected internal override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { if (context.ItemCount > 0) { var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height); var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); var realizationBounds = new UvBounds(Orientation, context.RealizationRect); var state = (WrapLayoutState)context.LayoutState; bool Arrange(WrapItem item, bool isLast = false) { if (item.Measure.HasValue == false) { return(false); } if (item.Position == null) { return(false); } var desiredMeasure = item.Measure.Value; if (desiredMeasure.U == 0) { return(true); // if an item is collapsed, avoid adding the spacing } UvMeasure position = item.Position.Value; // Stretch the last item to fill the available space if (isLast) { desiredMeasure.U = parentMeasure.U - position.U; } if (((position.V + desiredMeasure.V) >= realizationBounds.VMin) && (position.V <= realizationBounds.VMax)) { // place the item var child = context.GetOrCreateElementAt(item.Index); if (Orientation == Orientation.Horizontal) { child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V)); } else { child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U)); } } else if (position.V > realizationBounds.VMax) { return(false); } return(true); } for (var i = 0; i < context.ItemCount; i++) { bool continueArranging = Arrange(state.GetItemAt(i)); if (continueArranging == false) { break; } } } return(finalSize); }
/// <inheritdoc /> protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var totalMeasure = UvMeasure.Zero; var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height); var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); var realizationBounds = new UvBounds(Orientation, context.RealizationRect); var position = UvMeasure.Zero; var state = (WrapLayoutState)context.LayoutState; if (state.Orientation != Orientation) { state.SetOrientation(Orientation); } if (spacingMeasure.Equals(state.Spacing) == false) { state.ClearPositions(); state.Spacing = spacingMeasure; } if (state.AvailableU != parentMeasure.U) { state.ClearPositions(); state.AvailableU = parentMeasure.U; } double currentV = 0; for (int i = 0; i < context.ItemCount; i++) { bool measured = false; WrapItem item = state.GetItemAt(i); if (item.Measure == null) { item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(availableSize); item.Measure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height); measured = true; } UvMeasure currentMeasure = item.Measure.Value; if (currentMeasure.U == 0) { continue; // ignore collapsed items } if (item.Position == null) { if (parentMeasure.U < position.U + currentMeasure.U) { // New Row position.U = 0; position.V += currentV + spacingMeasure.V; currentV = 0; } item.Position = position; } position = item.Position.Value; double vEnd = position.V + currentMeasure.V; if (vEnd < realizationBounds.VMin) { // Item is "above" the bounds if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } } else if (position.V > realizationBounds.VMax) { // Item is "below" the bounds. if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } // We don't need to measure anything below the bounds break; } else if (measured == false) { // Always measure elements that are within the bounds item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(availableSize); currentMeasure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height); if (currentMeasure.Equals(item.Measure) == false) { // this item changed size; we need to recalculate layout for everything after this state.RemoveFromIndex(i + 1); item.Measure = currentMeasure; // did the change make it go into the new row? if (parentMeasure.U < position.U + currentMeasure.U) { // New Row position.U = 0; position.V += currentV + spacingMeasure.V; currentV = 0; } item.Position = position; } } position.U += currentMeasure.U + spacingMeasure.U; currentV = Math.Max(currentMeasure.V, currentV); } // update value with the last line // if the the last loop is(parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it // if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here // for the last condition it is zeros so adding it will make no difference // this way is faster than an if condition in every loop for checking the last item totalMeasure.U = parentMeasure.U; totalMeasure.V = state.GetHeight(); totalMeasure.U = Math.Ceiling(totalMeasure.U); return(Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U)); }