winrt::Size ScrollPresenter::ArrangeOverride()

in dev/ScrollPresenter/ScrollPresenter.cpp [663:962]


winrt::Size ScrollPresenter::ArrangeOverride(winrt::Size const& finalSize)
{
    SCROLLPRESENTER_TRACE_INFO(*this, TRACE_MSG_METH_STR_FLT_FLT, METH_NAME, this, L"finalSize", finalSize.Width, finalSize.Height);

    const winrt::UIElement content = Content();
    winrt::Rect finalContentRect{};

    // Possible cases:
    // 1. m_availableSize is infinite, the ScrollPresenter is not constrained and takes its Content DesiredSize.
    //    viewport thus is finalSize.
    // 2. m_availableSize > finalSize, the ScrollPresenter is constrained and its Content is smaller than the available size.
    //    No matter the ScrollPresenter's alignment, it does not grow larger than finalSize. viewport is finalSize again.
    // 3. m_availableSize <= finalSize, the ScrollPresenter is constrained and its Content is larger than or equal to
    //    the available size. viewport is the smaller & constrained m_availableSize.
    const winrt::Size viewport =
    {
        std::min(finalSize.Width, m_availableSize.Width),
        std::min(finalSize.Height, m_availableSize.Height)
    };

    bool renderSizeChanged = false;
    double newUnzoomedExtentWidth = 0.0;
    double newUnzoomedExtentHeight = 0.0;

    if (content)
    {
        float contentLayoutOffsetXDelta = 0.0f;
        float contentLayoutOffsetYDelta = 0.0f;
        bool isAnchoringElementHorizontally = false;
        bool isAnchoringElementVertically = false;
        bool isAnchoringFarEdgeHorizontally = false;
        bool isAnchoringFarEdgeVertically = false;
        const winrt::Size oldRenderSize = content.RenderSize();
        winrt::Size contentArrangeSize = content.DesiredSize();

        const winrt::FrameworkElement contentAsFE = content.try_as<winrt::FrameworkElement>();

        const winrt::Thickness contentMargin = [contentAsFE]()
        {
            return contentAsFE ? contentAsFE.Margin() : winrt::Thickness{ 0 };
        }();

        const bool wasContentArrangeWidthStretched = [contentAsFE, contentArrangeSize, viewport]()
        {
            return contentAsFE &&
                contentAsFE.HorizontalAlignment() == winrt::HorizontalAlignment::Stretch &&
                isnan(contentAsFE.Width()) &&
                contentArrangeSize.Width < viewport.Width;
        }();

        const bool wasContentArrangeHeightStretched = [contentAsFE, contentArrangeSize, viewport]()
        {
            return contentAsFE &&
                contentAsFE.VerticalAlignment() == winrt::VerticalAlignment::Stretch &&
                isnan(contentAsFE.Height()) &&
                contentArrangeSize.Height < viewport.Height;
        }();

        if (wasContentArrangeWidthStretched)
        {
            // Allow the content to stretch up to the larger viewport width.
            contentArrangeSize.Width = viewport.Width;
        }

        if (wasContentArrangeHeightStretched)
        {
            // Allow the content to stretch up to the larger viewport height.
            contentArrangeSize.Height = viewport.Height;
        }

        finalContentRect =
        {
            m_contentLayoutOffsetX,
            m_contentLayoutOffsetY,
            contentArrangeSize.Width,
            contentArrangeSize.Height
        };

        IsAnchoring(&isAnchoringElementHorizontally, &isAnchoringElementVertically, &isAnchoringFarEdgeHorizontally, &isAnchoringFarEdgeVertically);

        MUX_ASSERT(!(isAnchoringElementHorizontally && isAnchoringFarEdgeHorizontally));
        MUX_ASSERT(!(isAnchoringElementVertically && isAnchoringFarEdgeVertically));

        if (isAnchoringElementHorizontally || isAnchoringElementVertically || isAnchoringFarEdgeHorizontally || isAnchoringFarEdgeVertically)
        {
            MUX_ASSERT(m_interactionTracker);

            winrt::Size preArrangeViewportToElementAnchorPointsDistance{ FloatUtil::NaN, FloatUtil::NaN };

            if (isAnchoringElementHorizontally || isAnchoringElementVertically)
            {
                EnsureAnchorElementSelection();
                preArrangeViewportToElementAnchorPointsDistance = ComputeViewportToElementAnchorPointsDistance(
                    m_viewportWidth,
                    m_viewportHeight,
                    true /*isForPreArrange*/);
            }
            else
            {
                ResetAnchorElement();
            }

            contentArrangeSize = ArrangeContent(
                content,
                contentMargin,
                finalContentRect,
                wasContentArrangeWidthStretched,
                wasContentArrangeHeightStretched);

            if (!isnan(preArrangeViewportToElementAnchorPointsDistance.Width) || !isnan(preArrangeViewportToElementAnchorPointsDistance.Height))
            {
                // Using the new viewport sizes to handle the cases where an adjustment needs to be performed because of a ScrollPresenter size change.
                const winrt::Size postArrangeViewportToElementAnchorPointsDistance = ComputeViewportToElementAnchorPointsDistance(
                    viewport.Width /*viewportWidth*/,
                    viewport.Height /*viewportHeight*/,
                    false /*isForPreArrange*/);

                if (isAnchoringElementHorizontally &&
                    !isnan(preArrangeViewportToElementAnchorPointsDistance.Width) &&
                    !isnan(postArrangeViewportToElementAnchorPointsDistance.Width) &&
                    preArrangeViewportToElementAnchorPointsDistance.Width != postArrangeViewportToElementAnchorPointsDistance.Width)
                {
                    // Perform horizontal offset adjustment due to element anchoring
                    contentLayoutOffsetXDelta = ComputeContentLayoutOffsetDelta(
                        ScrollPresenterDimension::HorizontalScroll,
                        postArrangeViewportToElementAnchorPointsDistance.Width - preArrangeViewportToElementAnchorPointsDistance.Width /*unzoomedDelta*/);
                }

                if (isAnchoringElementVertically &&
                    !isnan(preArrangeViewportToElementAnchorPointsDistance.Height) &&
                    !isnan(postArrangeViewportToElementAnchorPointsDistance.Height) &&
                    preArrangeViewportToElementAnchorPointsDistance.Height != postArrangeViewportToElementAnchorPointsDistance.Height)
                {
                    // Perform vertical offset adjustment due to element anchoring
                    contentLayoutOffsetYDelta = ComputeContentLayoutOffsetDelta(
                        ScrollPresenterDimension::VerticalScroll,
                        postArrangeViewportToElementAnchorPointsDistance.Height - preArrangeViewportToElementAnchorPointsDistance.Height /*unzoomedDelta*/);
                }
            }
        }
        else
        {
            ResetAnchorElement();

            contentArrangeSize = ArrangeContent(
                content,
                contentMargin,
                finalContentRect,
                wasContentArrangeWidthStretched,
                wasContentArrangeHeightStretched);
        }

        newUnzoomedExtentWidth = contentArrangeSize.Width;
        newUnzoomedExtentHeight = contentArrangeSize.Height;

        double maxUnzoomedExtentWidth = std::numeric_limits<double>::infinity();
        double maxUnzoomedExtentHeight = std::numeric_limits<double>::infinity();

        if (contentAsFE)
        {
            // Determine the maximum size directly set on the content, if any.
            maxUnzoomedExtentWidth = GetComputedMaxWidth(maxUnzoomedExtentWidth, contentAsFE);
            maxUnzoomedExtentHeight = GetComputedMaxHeight(maxUnzoomedExtentHeight, contentAsFE);
        }

        // Take into account the actual resulting rendering size, in case it's larger than the desired size.
        // But the extent must not exceed the size explicitly set on the content, if any.
        newUnzoomedExtentWidth = std::max(
            newUnzoomedExtentWidth,
            std::max(0.0, content.RenderSize().Width + contentMargin.Left + contentMargin.Right));
        newUnzoomedExtentWidth = std::min(
            newUnzoomedExtentWidth,
            maxUnzoomedExtentWidth);

        newUnzoomedExtentHeight = std::max(
            newUnzoomedExtentHeight,
            std::max(0.0, content.RenderSize().Height + contentMargin.Top + contentMargin.Bottom));
        newUnzoomedExtentHeight = std::min(
            newUnzoomedExtentHeight,
            maxUnzoomedExtentHeight);

        if (isAnchoringFarEdgeHorizontally)
        {
            float unzoomedDelta = 0.0f;

            if (newUnzoomedExtentWidth > m_unzoomedExtentWidth ||                                  // ExtentWidth grew
                m_zoomedHorizontalOffset + m_viewportWidth > m_zoomFactor* m_unzoomedExtentWidth) // ExtentWidth shrank while overpanning
            {
                // Perform horizontal offset adjustment due to edge anchoring
                unzoomedDelta = static_cast<float>(newUnzoomedExtentWidth - m_unzoomedExtentWidth);
            }

            if (static_cast<float>(m_viewportWidth) > viewport.Width)
            {
                // Viewport width shrank: Perform horizontal offset adjustment due to edge anchoring
                unzoomedDelta += (static_cast<float>(m_viewportWidth) - viewport.Width) / m_zoomFactor;
            }

            if (unzoomedDelta != 0.0f)
            {
                MUX_ASSERT(contentLayoutOffsetXDelta == 0.0f);
                contentLayoutOffsetXDelta = ComputeContentLayoutOffsetDelta(ScrollPresenterDimension::HorizontalScroll, unzoomedDelta);
            }
        }

        if (isAnchoringFarEdgeVertically)
        {
            float unzoomedDelta = 0.0f;

            if (newUnzoomedExtentHeight > m_unzoomedExtentHeight ||                                // ExtentHeight grew
                m_zoomedVerticalOffset + m_viewportHeight > m_zoomFactor* m_unzoomedExtentHeight) // ExtentHeight shrank while overpanning
            {
                // Perform vertical offset adjustment due to edge anchoring
                unzoomedDelta = static_cast<float>(newUnzoomedExtentHeight - m_unzoomedExtentHeight);
            }

            if (static_cast<float>(m_viewportHeight) > viewport.Height)
            {
                // Viewport height shrank: Perform vertical offset adjustment due to edge anchoring
                unzoomedDelta += (static_cast<float>(m_viewportHeight) - viewport.Height) / m_zoomFactor;
            }

            if (unzoomedDelta != 0.0f)
            {
                MUX_ASSERT(contentLayoutOffsetYDelta == 0.0f);
                contentLayoutOffsetYDelta = ComputeContentLayoutOffsetDelta(ScrollPresenterDimension::VerticalScroll, unzoomedDelta);
            }
        }

        if (contentLayoutOffsetXDelta != 0.0f || contentLayoutOffsetYDelta != 0.0f)
        {
            const winrt::Rect contentRectWithDelta =
            {
                m_contentLayoutOffsetX + contentLayoutOffsetXDelta,
                m_contentLayoutOffsetY + contentLayoutOffsetYDelta,
                contentArrangeSize.Width,
                contentArrangeSize.Height
            };

            SCROLLPRESENTER_TRACE_INFO(*this, TRACE_MSG_METH_STR_STR, METH_NAME, this, L"content Arrange", TypeLogging::RectToString(contentRectWithDelta).c_str());
            content.Arrange(contentRectWithDelta);

            if (contentLayoutOffsetXDelta != 0.0f)
            {
                m_contentLayoutOffsetX += contentLayoutOffsetXDelta;
                UpdateOffset(ScrollPresenterDimension::HorizontalScroll, m_zoomedHorizontalOffset - contentLayoutOffsetXDelta);
                OnContentLayoutOffsetChanged(ScrollPresenterDimension::HorizontalScroll);
            }

            if (contentLayoutOffsetYDelta != 0.0f)
            {
                m_contentLayoutOffsetY += contentLayoutOffsetYDelta;
                UpdateOffset(ScrollPresenterDimension::VerticalScroll, m_zoomedVerticalOffset - contentLayoutOffsetYDelta);
                OnContentLayoutOffsetChanged(ScrollPresenterDimension::VerticalScroll);
            }

            OnViewChanged(contentLayoutOffsetXDelta != 0.0f /*horizontalOffsetChanged*/, contentLayoutOffsetYDelta != 0.0f /*verticalOffsetChanged*/);
        }

        renderSizeChanged = content.RenderSize() != oldRenderSize;
    }

    // Set a rectangular clip on this ScrollPresenter the same size as the arrange
    // rectangle so the content does not render beyond it.
    auto rectangleGeometry = Clip().as<winrt::RectangleGeometry>();

    if (!rectangleGeometry)
    {
        // Ensure that this ScrollPresenter has a rectangular clip.
        winrt::RectangleGeometry newRectangleGeometry;
        newRectangleGeometry.Rect();
        Clip(newRectangleGeometry);

        rectangleGeometry = newRectangleGeometry;
    }

    const winrt::Rect newClipRect{ 0.0f, 0.0f, viewport.Width, viewport.Height };
    rectangleGeometry.Rect(newClipRect);

    UpdateUnzoomedExtentAndViewport(
        renderSizeChanged,
        newUnzoomedExtentWidth  /*unzoomedExtentWidth*/,
        newUnzoomedExtentHeight /*unzoomedExtentHeight*/,
        viewport.Width          /*viewportWidth*/,
        viewport.Height         /*viewportHeight*/);

    // We do the following only when effective viewport
    // support is not available. This is to provide downlevel support.
    if (SharedHelpers::IsRS5OrHigher())
    {
        m_isAnchorElementDirty = true;
    }
    else
    {
        ClearAnchorCandidates();
        RaisePostArrange();
    }

    return viewport;
}