private void RaiseLineArranged() { var realizationRect = RealizationRect; if (realizationRect.Width != 0.0 || realizationRect.Height != 0.0) { int realizedElementCount = m_elementManager.GetRealizedElementCount(); if (realizedElementCount > 0) { Debug.Assert(m_firstRealizedDataIndexInsideRealizationWindow != -1 && m_lastRealizedDataIndexInsideRealizationWindow != -1); int countInLine = 0; var previousElementBounds = m_elementManager.GetLayoutBoundsForDataIndex(m_firstRealizedDataIndexInsideRealizationWindow); var currentLineOffset = OM.MajorStart(previousElementBounds); var currentLineSize = OM.MajorSize(previousElementBounds); for (int currentDataIndex = m_firstRealizedDataIndexInsideRealizationWindow; currentDataIndex <= m_lastRealizedDataIndexInsideRealizationWindow; currentDataIndex++) { var currentBounds = m_elementManager.GetLayoutBoundsForDataIndex(currentDataIndex); if (OM.MajorStart(currentBounds) != currentLineOffset) { // Staring a new line m_algorithmCallbacks.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, m_context); countInLine = 0; currentLineOffset = OM.MajorStart(currentBounds); currentLineSize = 0; } currentLineSize = Math.Max(currentLineSize, OM.MajorSize(currentBounds)); countInLine++; } // Raise for the last line. m_algorithmCallbacks.Algorithm_OnLineArranged(m_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, m_context); } } }
private bool ShouldContinueFillingUpSpace( int index, GenerateDirection direction) { bool shouldContinue; if (!IsVirtualizingContext) { shouldContinue = true; } else { var realizationRect = m_context.RealizationRect; var elementBounds = m_elementManager.GetLayoutBoundsForDataIndex(index); var elementMajorStart = OM.MajorStart(elementBounds); var elementMajorEnd = OM.MajorEnd(elementBounds); var rectMajorStart = OM.MajorStart(realizationRect); var rectMajorEnd = OM.MajorEnd(realizationRect); var elementMinorStart = OM.MinorStart(elementBounds); var elementMinorEnd = OM.MinorEnd(elementBounds); var rectMinorStart = OM.MinorStart(realizationRect); var rectMinorEnd = OM.MinorEnd(realizationRect); // Ensure that both minor and major directions are taken into consideration so that if the scrolling direction // is the same as the flow direction we still stop at the end of the viewport rectangle. shouldContinue = (direction == GenerateDirection.Forward && elementMajorStart < rectMajorEnd && elementMinorStart < rectMinorEnd) || (direction == GenerateDirection.Backward && elementMajorEnd > rectMajorStart && elementMinorEnd > rectMinorStart); } return(shouldContinue); }
private void ArrangeVirtualizingLayout( Size finalSize, LineAlignment lineAlignment, bool isWrapping, string layoutId) { // Walk through the realized elements one line at a time and // align them, Then call element.Arrange with the arranged bounds. int realizedElementCount = m_elementManager.GetRealizedElementCount(); if (realizedElementCount > 0) { int countInLine = 1; var previousElementBounds = m_elementManager.GetLayoutBoundsForRealizedIndex(0); var currentLineOffset = OM.MajorStart(previousElementBounds); var spaceAtLineStart = OM.MinorStart(previousElementBounds); double spaceAtLineEnd = 0; double currentLineSize = OM.MajorSize(previousElementBounds); for (int i = 1; i < realizedElementCount; i++) { var currentBounds = m_elementManager.GetLayoutBoundsForRealizedIndex(i); if (OM.MajorStart(currentBounds) != currentLineOffset) { spaceAtLineEnd = OM.Minor(finalSize) - OM.MinorStart(previousElementBounds) - OM.MinorSize(previousElementBounds); PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId); spaceAtLineStart = OM.MinorStart(currentBounds); countInLine = 0; currentLineOffset = OM.MajorStart(currentBounds); currentLineSize = 0; } countInLine++; // for current element currentLineSize = Math.Max(currentLineSize, OM.MajorSize(currentBounds)); previousElementBounds = currentBounds; } // Last line - potentially have a property to customize // aligning the last line or not. if (countInLine > 0) { double spaceAtEnd = OM.Minor(finalSize) - OM.MinorStart(previousElementBounds) - OM.MinorSize(previousElementBounds); PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId); } } }
private void Generate( GenerateDirection direction, int anchorIndex, Size availableSize, double minItemSpacing, double lineSpacing, uint maxItemsPerLine, bool disableVirtualization, string layoutId) { if (anchorIndex != -1) { int step = (direction == GenerateDirection.Forward) ? 1 : -1; int previousIndex = anchorIndex; int currentIndex = anchorIndex + step; var anchorBounds = m_elementManager.GetLayoutBoundsForDataIndex(anchorIndex); double lineOffset = OM.MajorStart(anchorBounds); double lineMajorSize = OM.MajorSize(anchorBounds); uint countInLine = 1; bool lineNeedsReposition = false; while (m_elementManager.IsIndexValidInData(currentIndex) && (disableVirtualization || ShouldContinueFillingUpSpace(previousIndex, direction))) { // Ensure layout element. m_elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId); var currentElement = m_elementManager.GetRealizedElement(currentIndex); var desiredSize = MeasureElement(currentElement, currentIndex, availableSize, m_context); // Lay it out. var previousElement = m_elementManager.GetRealizedElement(previousIndex); Rect currentBounds = new Rect(0, 0, desiredSize.Width, desiredSize.Height); var previousElementBounds = m_elementManager.GetLayoutBoundsForDataIndex(previousIndex); if (direction == GenerateDirection.Forward) { double remainingSpace = OM.Minor(availableSize) - (OM.MinorStart(previousElementBounds) + OM.MinorSize(previousElementBounds) + minItemSpacing + OM.Minor(desiredSize)); if (countInLine >= maxItemsPerLine || m_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) { // No more space in this row. wrap to next row. OM.SetMinorStart(ref currentBounds, 0); OM.SetMajorStart(ref currentBounds, OM.MajorStart(previousElementBounds) + lineMajorSize + lineSpacing); if (lineNeedsReposition) { // reposition the previous line (countInLine items) for (uint i = 0; i < countInLine; i++) { var dataIndex = currentIndex - 1 - i; var bounds = m_elementManager.GetLayoutBoundsForDataIndex((int)dataIndex); OM.SetMajorSize(ref bounds, lineMajorSize); m_elementManager.SetLayoutBoundsForDataIndex((int)dataIndex, bounds); } } // Setup for next line. lineMajorSize = OM.MajorSize(currentBounds); lineOffset = OM.MajorStart(currentBounds); lineNeedsReposition = false; countInLine = 1; } else { // More space is available in this row. OM.SetMinorStart(ref currentBounds, OM.MinorStart(previousElementBounds) + OM.MinorSize(previousElementBounds) + (minItemSpacing)); OM.SetMajorStart(ref currentBounds, lineOffset); lineMajorSize = Math.Max(lineMajorSize, OM.MajorSize(currentBounds)); lineNeedsReposition = OM.MajorSize(previousElementBounds) != OM.MajorSize(currentBounds); countInLine++; } } else { // Backward double remainingSpace = OM.MinorStart(previousElementBounds) - (OM.Minor(desiredSize) + minItemSpacing); if (countInLine >= maxItemsPerLine || m_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) { // Does not fit, wrap to the previous row var availableSizeMinor = OM.Minor(availableSize); OM.SetMinorStart(ref currentBounds, !double.IsInfinity(availableSizeMinor) ? availableSizeMinor - OM.Minor(desiredSize) : 0.0); OM.SetMajorStart(ref currentBounds, lineOffset - OM.Major(desiredSize) - lineSpacing); if (lineNeedsReposition) { var previousLineOffset = OM.MajorStart(m_elementManager.GetLayoutBoundsForDataIndex((int)(currentIndex + countInLine + 1))); // reposition the previous line (countInLine items) for (uint i = 0; i < countInLine; i++) { var dataIndex = currentIndex + 1 + (int)i; if (dataIndex != anchorIndex) { var bounds = m_elementManager.GetLayoutBoundsForDataIndex(dataIndex); OM.SetMajorStart(ref bounds, previousLineOffset - lineMajorSize - lineSpacing); OM.SetMajorSize(ref bounds, lineMajorSize); m_elementManager.SetLayoutBoundsForDataIndex(dataIndex, bounds); } } } // Setup for next line. lineMajorSize = OM.MajorSize(currentBounds); lineOffset = OM.MajorStart(currentBounds); lineNeedsReposition = false; countInLine = 1; } else { // Fits in this row. put it in the previous position OM.SetMinorStart(ref currentBounds, OM.MinorStart(previousElementBounds) - OM.Minor(desiredSize) - minItemSpacing); OM.SetMajorStart(ref currentBounds, lineOffset); lineMajorSize = Math.Max(lineMajorSize, OM.MajorSize(currentBounds)); lineNeedsReposition = OM.MajorSize(previousElementBounds) != OM.MajorSize(currentBounds); countInLine++; } } m_elementManager.SetLayoutBoundsForDataIndex(currentIndex, currentBounds); previousIndex = currentIndex; currentIndex += step; } // If we did not reach the top or bottom of the extent, we realized one // extra item before we knew we were outside the realization window. Do not // account for that element in the indicies inside the realization window. if (direction == GenerateDirection.Forward) { int dataCount = m_context.ItemCount; m_lastRealizedDataIndexInsideRealizationWindow = previousIndex == dataCount - 1 ? dataCount - 1 : previousIndex - 1; m_lastRealizedDataIndexInsideRealizationWindow = Math.Max(0, m_lastRealizedDataIndexInsideRealizationWindow); } else { int dataCount = m_context.ItemCount; m_firstRealizedDataIndexInsideRealizationWindow = previousIndex == 0 ? 0 : previousIndex + 1; m_firstRealizedDataIndexInsideRealizationWindow = Math.Min(dataCount - 1, m_firstRealizedDataIndexInsideRealizationWindow); } m_elementManager.DiscardElementsOutsideWindow(direction == GenerateDirection.Forward, currentIndex); } }
private int GetAnchorIndex( Size availableSize, bool isWrapping, double minItemSpacing, string layoutId) { int anchorIndex = -1; Point anchorPosition = default; if (!IsVirtualizingContext) { // Non virtualizing host, start generating from the element 0 anchorIndex = ((IVirtualizingLayoutContextOverrides)m_context).ItemCountCore() > 0 ? 0 : -1; } else { bool isRealizationWindowConnected = m_elementManager.IsWindowConnected(RealizationRect, OM.ScrollOrientation, m_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 && ( OM.Minor(m_lastAvailableSize) != OM.Minor(availableSize) || m_lastItemSpacing != minItemSpacing || m_collectionChangePending); var suggestedAnchorIndex = m_context.RecommendedAnchorIndex; bool isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && m_elementManager.IsDataIndexRealized(suggestedAnchorIndex); if (isAnchorSuggestionValid) { anchorIndex = m_algorithmCallbacks.Algorithm_GetAnchorForTargetElement( suggestedAnchorIndex, availableSize, m_context).Index; if (m_elementManager.IsDataIndexRealized(anchorIndex)) { var anchorBounds = m_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 = OM.MinorMajorPoint(0, OM.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 = m_elementManager.GetDataIndexFromRealizedRangeIndex(0); Debug.Assert(anchorIndex < firstRealizedDataIndex); for (int i = firstRealizedDataIndex - 1; i >= anchorIndex; --i) { m_elementManager.EnsureElementRealized(false /*forward*/, i, layoutId); } var anchorBounds = m_elementManager.GetLayoutBoundsForDataIndex(suggestedAnchorIndex); anchorPosition = OM.MinorMajorPoint(0, OM.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 = m_algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, m_context); anchorIndex = anchorInfo.Index; anchorPosition = OM.MinorMajorPoint(0, anchorInfo.Offset); } else { // No suggestion - just pick first in realized range anchorIndex = m_elementManager.GetDataIndexFromRealizedRangeIndex(0); var firstElementBounds = m_elementManager.GetLayoutBoundsForRealizedIndex(0); anchorPosition = new Point(firstElementBounds.X, firstElementBounds.Y); } } Debug.Assert(anchorIndex == -1 || m_elementManager.IsIndexValidInData(anchorIndex)); m_firstRealizedDataIndexInsideRealizationWindow = m_lastRealizedDataIndexInsideRealizationWindow = anchorIndex; if (m_elementManager.IsIndexValidInData(anchorIndex)) { if (!m_elementManager.IsDataIndexRealized(anchorIndex)) { // Disconnected, throw everything and create new anchor m_elementManager.ClearRealizedRange(); var anchor = m_context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); m_elementManager.Add(anchor, anchorIndex); } var anchorElement = m_elementManager.GetRealizedElement(anchorIndex); var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, m_context); var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height); m_elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds); } else { // Throw everything away m_elementManager.ClearRealizedRange(); } // TODO: Perhaps we can track changes in the property setter m_lastAvailableSize = availableSize; m_lastItemSpacing = minItemSpacing; return(anchorIndex); }