double GetAverageLineInfo( Size availableSize, VirtualizingLayoutContext context, FlowLayoutState flowState, double avgCountInLine) { // default to 1 item per line with 0 size double avgLineSize = 0; avgCountInLine = 1; MUX_ASSERT(context.ItemCount > 0); if (flowState.TotalLinesMeasured == 0) { var tmpElement = context.GetOrCreateElementAt(0, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); var desiredSize = flowState.FlowAlgorithm.MeasureElement(tmpElement, 0, availableSize, context); context.RecycleElement(tmpElement); int estimatedCountInLine = Math.Max(1, (int)(Minor(availableSize) / Minor(desiredSize))); flowState.OnLineArranged(0, estimatedCountInLine, Major(desiredSize), context); flowState.SpecialElementDesiredSize = desiredSize; } avgCountInLine = Math.Max(1.0, flowState.TotalItemsPerLine / flowState.TotalLinesMeasured); avgLineSize = Math.Round(flowState.TotalLineSize / flowState.TotalLinesMeasured); return(_uno_lastKnownAverageLineSize = avgLineSize); }
public UIElement GetAt(int realizedIndex) { UIElement element; if (IsVirtualizingContext) { if (!m_realizedElements.TryGetElementAt(realizedIndex, out element)) { // Sentinel. Create the element now since we need it. int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex); REPEATER_TRACE_INFO("Creating element for sentinal with data index %d. \n", dataIndex); element = m_context.GetOrCreateElementAt(dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); m_realizedElements[realizedIndex] = element; } } else { // realizedIndex and dataIndex are the same (everything is realized) element = m_context.GetOrCreateElementAt(realizedIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); } return(element); }
void MakeAnchor( VirtualizingLayoutContext context, int index, Size availableSize) { m_elementManager.ClearRealizedRange(); // FlowLayout requires that the anchor is the first element in the row. var internalAnchor = m_algorithmCallbacks.Algorithm_GetAnchorForTargetElement(index, availableSize, context); global::System.Diagnostics.Debug.Assert(internalAnchor.Index <= index); // 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(m_algorithmCallbacks.Algorithm_GetMeasureSize(dataIndex, availableSize, context)); m_elementManager.Add(element, dataIndex); } }
double GetAverageElementSize( Size availableSize, VirtualizingLayoutContext context, StackLayoutState stackLayoutState) { double averageElementSize = 0; if (context.ItemCount > 0) { if (stackLayoutState.TotalElementsMeasured == 0) { var tmpElement = context.GetOrCreateElementAt(0, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); stackLayoutState.FlowAlgorithm.MeasureElement(tmpElement, 0, availableSize, context); context.RecycleElement(tmpElement); } MUX_ASSERT(stackLayoutState.TotalElementsMeasured > 0); averageElementSize = Math.Round(stackLayoutState.TotalElementSize / stackLayoutState.TotalElementsMeasured); } return(averageElementSize); }
int GetAnchorIndex( Size availableSize, bool isWrapping, double minItemSpacing, string layoutId) { int anchorIndex = -1; Point anchorPosition = default; var context = m_context; if (!IsVirtualizingContext) { // Non virtualizing host, start generating from the element 0 anchorIndex = context.ItemCount > 0 ? 0 : -1; } else { bool isRealizationWindowConnected = m_elementManager.IsWindowConnected(RealizationRect, 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 && ( Minor(m_lastAvailableSize) != Minor(availableSize) || m_lastItemSpacing != minItemSpacing || m_collectionChangePending); var suggestedAnchorIndex = m_context.RecommendedAnchorIndex; bool isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && m_elementManager.IsDataIndexRealized(suggestedAnchorIndex); if (isAnchorSuggestionValid) { REPEATER_TRACE_INFO("%*s: \tUsing suggested anchor %d\n", context.Indent, layoutId, suggestedAnchorIndex); anchorIndex = m_algorithmCallbacks.Algorithm_GetAnchorForTargetElement( suggestedAnchorIndex, availableSize, 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 = MinorMajorPoint(0, (float)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); global::System.Diagnostics.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 = MinorMajorPoint(0, (float)MajorStart(anchorBounds)); } } else if (needAnchorColumnRevaluation || !isRealizationWindowConnected) { if (needAnchorColumnRevaluation) { REPEATER_TRACE_INFO("%*s: \tNeedAnchorColumnReevaluation \n", context.Indent, layoutId); } if (!isRealizationWindowConnected) { REPEATER_TRACE_INFO("%*s: \tDisconnected Window \n", context.Indent, layoutId); } // 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, context); anchorIndex = anchorInfo.Index; anchorPosition = MinorMajorPoint(0, (float)(anchorInfo.Offset)); } else { REPEATER_TRACE_INFO("%*s: \tConnected Window - picking first realized element as anchor \n", context.Indent, layoutId); // 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); } } REPEATER_TRACE_INFO("%*s: \tPicked anchor:%d \n", context.Indent, layoutId, anchorIndex); global::System.Diagnostics.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 REPEATER_TRACE_INFO("%*s Disconnected Window - throwing away all realized elements \n", context.Indent, layoutId); 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); REPEATER_TRACE_INFO("%*s: \tLayout bounds of anchor %d are (%.0f,%.0f,%.0f,%.0f). \n", context.Indent, layoutId, anchorIndex, layoutBounds.X, layoutBounds.Y, layoutBounds.Width, layoutBounds.Height); } else { // Throw everything away REPEATER_TRACE_INFO("%*s \tAnchor index is not valid - throwing away all realized elements \n", context.Indent, layoutId); m_elementManager.ClearRealizedRange(); } // TODO: Perhaps we can track changes in the property setter m_lastAvailableSize = availableSize; m_lastItemSpacing = minItemSpacing; return(anchorIndex); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { if (this.MinItemSize == Size.Empty) { var firstElement = context.GetOrCreateElementAt(0); firstElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); // setting the member value directly to skip invalidating layout this._minItemSize = firstElement.DesiredSize; } // Determine which rows need to be realized. We know every row will have the same height and // only contain 3 items. Use that to determine the index for the first and last item that // will be within that realization rect. var firstRowIndex = Math.Max( (int)(context.RealizationRect.Y / (this.MinItemSize.Height + this.RowSpacing)) - 1, 0); var lastRowIndex = Math.Min( (int)(context.RealizationRect.Bottom / (this.MinItemSize.Height + this.RowSpacing)) + 1, (int)(context.ItemCount / 3)); // Determine which items will appear on those rows and what the rect will be for each item var state = context.LayoutState as ActivityFeedLayoutState; state.LayoutRects.Clear(); // Save the index of the first realized item. We'll use it as a starting point during arrange. state.FirstRealizedIndex = firstRowIndex * 3; // ideal item width that will expand/shrink to fill available space double desiredItemWidth = Math.Max(this.MinItemSize.Width, (availableSize.Width - this.ColumnSpacing * 3) / 4); // Foreach item between the first and last index, // Call GetElementOrCreateElementAt which causes an element to either be realized or retrieved // from a recycle pool // Measure the element using an appropriate size // // Any element that was previously realized which we don't retrieve in this pass (via a call to // GetElementOrCreateAt) will be automatically cleared and set aside for later re-use. // Note: While this work fine, it does mean that more elements than are required may be // created because it isn't until after our MeasureOverride completes that the unused elements // will be recycled and available to use. We could avoid this by choosing to track the first/last // index from the previous layout pass. The diff between the previous range and current range // would represent the elements that we can pre-emptively make available for re-use by calling // context.RecycleElement(element). for (int rowIndex = firstRowIndex; rowIndex < lastRowIndex; rowIndex++) { int firstItemIndex = rowIndex * 3; var boundsForCurrentRow = CalculateLayoutBoundsForRow(rowIndex, desiredItemWidth); for (int columnIndex = 0; columnIndex < 3; columnIndex++) { var index = firstItemIndex + columnIndex; var rect = boundsForCurrentRow[index % 3]; var container = context.GetOrCreateElementAt(index); container.Measure( new Size(boundsForCurrentRow[columnIndex].Width, boundsForCurrentRow[columnIndex].Height)); state.LayoutRects.Add(boundsForCurrentRow[columnIndex]); } } // Calculate and return the size of all the content (realized or not) by figuring out // what the bottom/right position of the last item would be. var extentHeight = ((int)(context.ItemCount / 3) - 1) * (this.MinItemSize.Height + this.RowSpacing) + this.MinItemSize.Height; // Report this as the desired size for the layout return(new Size(desiredItemWidth * 4 + this.ColumnSpacing * 2, extentHeight)); }
public UIElement this[int index] => m_context.GetOrCreateElementAt(index, ElementRealizationOptions.None);