public Size Measure(
            Size availableSize,
            VirtualizingLayoutContext context,
            bool isWrapping,
            double minItemSpacing,
            double lineSpacing,
            uint maxItemsPerLine,
            ScrollOrientation orientation,
            bool disableVirtualization,
            string layoutId)
        {
            OM.ScrollOrientation = orientation;

            // If minor size is infinity, there is only one line and no need to align that line.
            m_scrollOrientationSameAsFlow = double.IsInfinity(OM.Minor(availableSize));

            var suggestedAnchorIndex = m_context.RecommendedAnchorIndex;

            if (m_elementManager.IsIndexValidInData(suggestedAnchorIndex))
            {
                var anchorRealized = m_elementManager.IsDataIndexRealized(suggestedAnchorIndex);
                if (!anchorRealized)
                {
                    MakeAnchor(m_context, suggestedAnchorIndex, availableSize);
                }
            }

            m_elementManager.OnBeginMeasure(orientation);

            int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId);

            Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId);
            Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId);
            if (isWrapping && IsReflowRequired)
            {
                var firstElementBounds = m_elementManager.GetLayoutBoundsForRealizedIndex(0);
                OM.SetMinorStart(ref firstElementBounds, 0);
                m_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
                Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId);
            }

            RaiseLineArranged();
            m_collectionChangePending = false;
            m_lastExtent = EstimateExtent(availableSize, layoutId);
            SetLayoutOrigin();

            return(new Size(m_lastExtent.Width, m_lastExtent.Height));
        }
        // Align elements within a line. Note that this does not modify LayoutBounds. So if we get
        // repeated measures, the LayoutBounds remain the same in each layout.
        private void PerformLineAlignment(
            int lineStartIndex,
            int countInLine,
            double spaceAtLineStart,
            double spaceAtLineEnd,
            double lineSize,
            LineAlignment lineAlignment,
            bool isWrapping,
            Size finalSize,
            string layoutId)
        {
            for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex)
            {
                var bounds = m_elementManager.GetLayoutBoundsForRealizedIndex(rangeIndex);
                OM.SetMajorSize(ref bounds, lineSize);

                if (!m_scrollOrientationSameAsFlow)
                {
                    // Note: Space at start could potentially be negative
                    if (spaceAtLineStart != 0 || spaceAtLineEnd != 0)
                    {
                        double totalSpace = spaceAtLineStart + spaceAtLineEnd;
                        switch (lineAlignment)
                        {
                        case LineAlignment.Start:
                        {
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) - spaceAtLineStart);
                            break;
                        }

                        case LineAlignment.End:
                        {
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) + spaceAtLineEnd);
                            break;
                        }

                        case LineAlignment.Center:
                        {
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) - spaceAtLineStart);
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) + totalSpace / 2);
                            break;
                        }

                        case LineAlignment.SpaceAround:
                        {
                            double interItemSpace = countInLine >= 1 ? totalSpace / (countInLine * 2) : 0;
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) - spaceAtLineStart);
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) + interItemSpace * ((rangeIndex - lineStartIndex + 1) * 2 - 1));
                            break;
                        }

                        case LineAlignment.SpaceBetween:
                        {
                            double interItemSpace = countInLine > 1 ? totalSpace / (countInLine - 1) : 0;
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) - spaceAtLineStart);
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) + interItemSpace * (rangeIndex - lineStartIndex));
                            break;
                        }

                        case LineAlignment.SpaceEvenly:
                        {
                            double interItemSpace = countInLine >= 1 ? totalSpace / (countInLine + 1) : 0;
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) - spaceAtLineStart);
                            OM.SetMinorStart(ref bounds, OM.MinorStart(bounds) + interItemSpace * (rangeIndex - lineStartIndex + 1));
                            break;
                        }
                        }
                    }
                }

                bounds.X -= m_lastExtent.X;
                bounds.Y -= m_lastExtent.Y;

                if (!isWrapping)
                {
                    OM.SetMinorSize(ref bounds, Math.Max(OM.MinorSize(bounds), OM.Minor(finalSize)));
                }

                var element = m_elementManager.GetAt(rangeIndex);

                element.Arrange(bounds);
            }
        }
        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);
            }
        }