void SortElements(Rect visibleWindow) { // Sort in descending order (inVisibleWindow, phase) m_pendingElements.Sort((lhs, rhs) => { var lhsBounds = lhs.VirtInfo.ArrangeBounds; var lhsIntersects = SharedHelpers.DoRectsIntersect(lhsBounds, visibleWindow); var rhsBounds = rhs.VirtInfo.ArrangeBounds; var rhsIntersects = SharedHelpers.DoRectsIntersect(rhsBounds, visibleWindow); if ((lhsIntersects && rhsIntersects) || (!lhsIntersects && !rhsIntersects)) { // Both are in the visible window or both are not return(lhs.VirtInfo.Phase - rhs.VirtInfo.Phase); } else if (lhsIntersects) { // Left is in visible window return(-1); } else { return(1); } }); }
void DoPhasedWorkCallback() { MarkCallbackRecieved(); if (m_pendingElements.Count > 0 && !BuildTreeScheduler.ShouldYield()) { var visibleWindow = m_owner.VisibleWindow; SortElements(visibleWindow); int currentIndex = m_pendingElements.Count - 1; do { var info = m_pendingElements[currentIndex]; var element = info.Element; var virtInfo = info.VirtInfo; var dataIndex = virtInfo.Index; int currentPhase = virtInfo.Phase; if (currentPhase > 0) { int nextPhase = VirtualizationInfo.PhaseReachedEnd; virtInfo.DataTemplateComponent.ProcessBindings(virtInfo.Data, -1 /* item index unused */, currentPhase, out nextPhase); ValidatePhaseOrdering(currentPhase, nextPhase); var previousAvailableSize = LayoutInformation.GetAvailableSize(element); element.Measure(previousAvailableSize); if (nextPhase > 0) { virtInfo.Phase = nextPhase; // If we are the first item or // If the current items phase is higher than the next items phase, then move to the next item. if (currentIndex == 0 || virtInfo.Phase > m_pendingElements[currentIndex - 1].VirtInfo.Phase) { currentIndex--; } } else { m_pendingElements.RemoveAt(currentIndex); currentIndex--; } } else { throw new InvalidOperationException("Cleared element found in pending list which is not expected"); } var pendingCount = (int)(m_pendingElements.Count); if (currentIndex == -1) { // Reached the top, start from the bottom again currentIndex = pendingCount - 1; } else if (currentIndex > -1 && currentIndex < pendingCount - 1) { // If the next element is oustide the visible window and there are elements in the visible window // go back to the visible window. bool nextItemIsVisible = SharedHelpers.DoRectsIntersect(visibleWindow, m_pendingElements[currentIndex].VirtInfo.ArrangeBounds); if (!nextItemIsVisible) { bool haveVisibleItems = SharedHelpers.DoRectsIntersect(visibleWindow, m_pendingElements[pendingCount - 1].VirtInfo.ArrangeBounds); if (haveVisibleItems) { currentIndex = pendingCount - 1; } } } } while (m_pendingElements.Count > 0 && !BuildTreeScheduler.ShouldYield()); } if (m_pendingElements.Count > 0) { RegisterForCallback(); } }
// BringIntoView functionality is ported from WinUI ScrollPresenter // https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/ScrollPresenter/ScrollPresenter.cpp // with partial modifications to match the ScrollViewer control behavior. protected override void OnBringIntoViewRequested(BringIntoViewRequestedEventArgs args) { base.OnBringIntoViewRequested(args); UIElement content = RealContent as UIElement; if (args.Handled || args.TargetElement == this || (args.TargetElement == content && content?.Visibility == Visibility.Collapsed) || !SharedHelpers.IsAncestor(args.TargetElement, this, true /*checkVisibility*/)) { // Ignore the request when: // - It was handled already. // - The target element is this ScrollPresenter itself. A parent scrollPresenter may fulfill the request instead then. // - The target element is effectively collapsed within the ScrollPresenter. return; } Rect targetRect = new Rect(); double targetZoomedHorizontalOffset = 0.0; double targetZoomedVerticalOffset = 0.0; double appliedOffsetX = 0.0; double appliedOffsetY = 0.0; var viewportWidth = ViewportWidth; var viewportHeight = ViewportHeight; var zoomFactor = Scroller.ZoomFactor; // Compute the target offsets based on the provided BringIntoViewRequestedEventArgs. ComputeBringIntoViewTargetOffsets( content, args, out targetZoomedHorizontalOffset, out targetZoomedVerticalOffset, out appliedOffsetX, out appliedOffsetY, out targetRect); // Do not include the applied offsets so that potential parent bring-into-view contributors ignore that shift. Rect nextTargetRect = new Rect( targetRect.X * zoomFactor - targetZoomedHorizontalOffset - appliedOffsetX, targetRect.Y * zoomFactor - targetZoomedVerticalOffset - appliedOffsetY, Math.Min(targetRect.Width * zoomFactor, viewportWidth), Math.Min(targetRect.Height * zoomFactor, viewportHeight)); Rect viewportRect = new Rect( 0.0f, 0.0f, (float)viewportWidth, (float)viewportHeight); var verticalOffset = Scroller.VerticalOffset; var horizontalOffset = Scroller.HorizontalOffset; var zoomedVerticalOffset = verticalOffset; var zoomedHorizontalOffset = horizontalOffset; if (targetZoomedHorizontalOffset != zoomedHorizontalOffset || targetZoomedVerticalOffset != zoomedVerticalOffset) { Scroller.ChangeView(targetZoomedHorizontalOffset, targetZoomedVerticalOffset, zoomFactor, !args.AnimationDesired); } else { // No offset change was triggered because the target offsets are the same as the current ones. Mark the operation as completed immediately. //RaiseViewChangeCompleted(true /*isForScroll*/, ScrollPresenterViewChangeResult::Completed, offsetsChangeCorrelationId); } if (SharedHelpers.DoRectsIntersect(nextTargetRect, viewportRect)) { // Next bring a portion of this ScrollPresenter into view. args.TargetRect = nextTargetRect; args.TargetElement = Scroller; args.HorizontalOffset = args.HorizontalOffset - appliedOffsetX; args.VerticalOffset = args.VerticalOffset - appliedOffsetY; } else { // This ScrollPresenter did not even partially bring the TargetRect into its viewport. // Mark the operation as handled since no portion of this ScrollPresenter needs to be brought into view. args.Handled = true; } }