internal static void DistributeRoundingError(FlexElementCollection elements, double roundingError, bool starsOnly = false) { if (roundingError.IsZero(XamlConstants.LayoutComparisonPrecision)) { return; } var orientation = elements.Orientation; roundingError = roundingError > 0 ? roundingError.LayoutRound(orientation, RoundingMode.FromZero) : roundingError.LayoutRound(orientation, RoundingMode.ToZero); var roundingErrorSign = Math.Sign(roundingError); var count = elements.Count; for (var i = count - 1; i > 0; i--) { var flexElement = elements[i]; if (starsOnly && flexElement.IsStar == false) { continue; } if (DistributeRoundingError(ref roundingError, ref flexElement)) { elements[i] = flexElement; } if (roundingErrorSign != Math.Sign(roundingError)) { break; } } }
private double Sort(FlexElementCollection elements, FlexDistributeDirection direction) { ArrayUtils.EnsureArrayLength(ref _sortedElements, elements.Count, false); ArrayUtils.EnsureArrayLength(ref _sortedIndex, elements.Count, false); _actualCount = 0; var fixedLength = 0.0; for (var index = elements.Count - 1; index >= 0; index--) { var item = elements[index]; if (item.CanDistribute(direction) == false) { fixedLength += item.ActualLength; continue; } _sortedElements[_actualCount] = item; _sortedIndex[_actualCount] = index; _actualCount++; } Array.Sort(_sortedElements, _sortedIndex, 0, _actualCount, FlexItemComparer.Default); _currentCount = _actualCount; return(fixedLength); }
private void RestoreOrder(FlexElementCollection elements) { for (var i = 0; i < _currentCount; i++) { var item = _sortedElements[i]; var index = _sortedIndex[i]; elements[index] = item; } }
public void Distribute(FlexElementCollection elements, double target) { var elementsActual = elements.Actual; if (elementsActual.IsCloseTo(target, XamlConstants.LayoutComparisonPrecision)) { return; } var distribution = elementsActual.IsGreaterThan(target) ? FlexDistributeDirection.Shrink : FlexDistributeDirection.Expand; if (distribution == FlexDistributeDirection.Shrink) { switch (_mode) { case Mode.Equal: ShrinkAll(elements, target); break; case Mode.First: ShrinkFromStart(elements, target); break; case Mode.Last: ShrinkFromEnd(elements, target); break; default: throw new ArgumentOutOfRangeException(); } } else { switch (_mode) { case Mode.Equal: ExpandAll(elements, target); break; case Mode.First: ExpandFromStart(elements, target); break; case Mode.Last: ExpandFromEnd(elements, target); break; default: throw new ArgumentOutOfRangeException(); } } }
private void RemoveAt(FlexElementCollection elements, int index) { var item = _sortedElements[index]; var actualIndex = _sortedIndex[index]; elements[actualIndex] = item; for (var iItem = index; iItem < _currentCount - 1; iItem++) { _sortedElements[iItem] = _sortedElements[iItem + 1]; _sortedIndex[iItem] = _sortedIndex[iItem + 1]; } _currentCount--; }
private double Stretch(FlexElementCollection flexElements, double spacing, double availableDirect, bool skipHiddenSpacing) { if (flexElements.Count == 0) { return(0.0); } var stretch = Panel.Stretch; var target = availableDirect; var spacingDelta = CalcSpacingDelta(GetVisibleCount(Panel, skipHiddenSpacing), spacing); if (spacingDelta.Equals(0.0) == false) { target = Math.Max(0, target - spacingDelta); } return(flexElements.Stretch(stretch, target, Panel.Distributor) + spacingDelta); }
private static void ShrinkFromStart(FlexElementCollection elements, double target) { var useLayoutRounding = elements.UseLayoutRounding; if (useLayoutRounding) { target = target.LayoutRound(elements.Orientation, RoundingMode.MidPointFromZero); } if (elements.TryShrinkToMinimum(target)) { return; } var current = elements.Actual; if (current.IsLessThanOrClose(elements.ActualMinimum)) { return; } for (var index = 0; index < elements.Count; index++) { var item = elements[index]; var compensation = Math.Min(current - target, item.ActualLength - item.ActualMinLength); item.ActualLength -= compensation; elements[index] = item; current -= compensation; if (current.IsCloseTo(target, XamlConstants.LayoutComparisonPrecision)) { break; } } }
private static void ExpandFromEnd(FlexElementCollection elements, double target) { var useLayoutRounding = elements.UseLayoutRounding; if (useLayoutRounding) { target = target.LayoutRound(elements.Orientation, RoundingMode.MidPointFromZero); } if (elements.TryExpandToMaximum(target)) { return; } var current = elements.Actual; if (current.IsGreaterThanOrClose(elements.ActualMaximum)) { return; } for (var index = elements.Count - 1; index >= 0; index--) { var item = elements[index]; var compensation = Math.Max(-item.ActualLength, Math.Min(target - current, item.ActualMaxLength - item.ActualLength)); item.ActualLength += compensation; elements[index] = item; current += compensation; if (current.IsCloseTo(target, XamlConstants.LayoutComparisonPrecision)) { break; } } }
private void Distribute(FlexElementCollection elements, FlexDistributeDirection direction, double target) { ArrayUtils.EnsureArrayLength(ref _index, elements.Count, false); _priorityStack.Clear(); _priorityStack.Push(0); foreach (var flexItem in elements) { var priority = flexItem.GetPriority(direction); if (priority > _priorityStack.Peek()) { _priorityStack.Push(priority); } } while (_priorityStack.Count > 0) { var priority = _priorityStack.Pop(); _elementsCopy.EnsureCount(elements.Count); _elementsCopy.UseLayoutRounding = elements.UseLayoutRounding; var currentTarget = target; var j = 0; for (var i = 0; i < elements.Count; i++) { var flexItem = elements[i]; if (flexItem.GetPriority(direction) < priority) { currentTarget -= flexItem.ActualLength; continue; } _index[j] = i; _elementsCopy[j] = flexItem; j++; } _elementsCopy.EnsureCount(j); currentTarget = Math.Max(0, currentTarget); if (direction == FlexDistributeDirection.Expand) { ExpandAllImpl(_elementsCopy, currentTarget); } else { ShrinkAllImpl(_elementsCopy, currentTarget); } for (var i = 0; i < _elementsCopy.Count; i++) { var flexItem = _elementsCopy[i]; var index = _index[i]; elements[index] = flexItem; } if (elements.Actual.IsCloseTo(target)) { return; } } }
private void ShrinkAllImpl(FlexElementCollection elements, double target) { var useLayoutRounding = elements.UseLayoutRounding; var originalTarget = target; var orientation = elements.Orientation; if (useLayoutRounding) { target = target.LayoutRound(orientation, RoundingMode.MidPointFromZero); } if (elements.TryShrinkToMinimum(target)) { return; } target -= Sort(elements, FlexDistributeDirection.Shrink); try { var deadlock = true; do { var tmpTarget = target; for (var iItem = 0; iItem < _currentCount; iItem++) { var item = _sortedElements[iItem]; if (item.ActualLength * (_currentCount - iItem) >= tmpTarget) { deadlock = false; var success = true; var avg = tmpTarget / (_currentCount - iItem); if (useLayoutRounding) { avg = avg.LayoutRound(orientation, RoundingMode.MidPointFromZero); } for (var jItem = iItem; jItem < _currentCount; jItem++) { var titem = _sortedElements[jItem]; if (avg > titem.MinLength) { titem.ActualLength = avg; } else { titem.ActualLength = titem.MinLength; success = false; } _sortedElements[jItem] = titem; if (success) { continue; } target -= titem.ActualLength; RemoveAt(elements, jItem); jItem--; } if (success) { return; } break; } tmpTarget -= item.ActualLength; } } while (_currentCount > 0 && deadlock == false); } finally { RestoreOrder(elements); DistributeRoundingError(elements, elements.Actual - originalTarget); } }
private void ShrinkAll(FlexElementCollection elements, double target) { Distribute(elements, FlexDistributeDirection.Shrink, target); }
private void ExpandAll(FlexElementCollection elements, double target) { Distribute(elements, FlexDistributeDirection.Expand, target); }
private OrientedSize ProcessSpacingAndOverflow(double spacing, OrientedSize desiredOriented, OrientedSize availableOriented, out bool isHiddenChanged) { var childrenCount = Panel.Elements.Count; var orientation = Panel.Orientation; var target = availableOriented.Direct; isHiddenChanged = false; // Current length is greater than available and we have no possibility to stretch down -> mark elements as hidden var current = 0.0; var visible = 0.0; var hasHiddenChildren = false; var visibleChildrenCount = 0; if (desiredOriented.Direct.IsGreaterThan(availableOriented.Direct)) { var stretchOverflow = FlexElementCollection.Mount(FlexElements.Capacity); try { // Process Pinned Flexible for (var index = 0; index < childrenCount; index++) { var flexElement = FlexElements[index]; if (CanPinStretch(flexElement) == false) { continue; } flexElement.StretchDirection = FlexStretchDirection.Shrink; stretchOverflow.Add(flexElement); Panel.SetIsHidden(Panel.Elements[index], false); } // Process Pinned for (var index = 0; index < childrenCount; index++) { var child = Panel.Elements[index]; if (child.Visibility == Visibility.Collapsed) { continue; } var flexElement = FlexElements[index]; if (CanPin(flexElement) == false) { continue; } current += flexElement.ActualLength; current += spacing; visible = current; Panel.SetIsHidden(Panel.Elements[index], false); } // Process Hide for (var index = 0; index < childrenCount; index++) { var child = Panel.Elements[index]; if (child.Visibility == Visibility.Collapsed) { continue; } visibleChildrenCount++; var flexElement = FlexElements[index]; if (CanPin(flexElement)) { continue; } current += flexElement.ActualLength; if (CanHide(flexElement) == false) { isHiddenChanged |= Panel.GetIsHidden(child); Panel.SetIsHidden(child, false); current += spacing; visible = current; continue; } var isOverflowed = current.IsGreaterThan(target, XamlConstants.LayoutComparisonPrecision); current += spacing; if (isOverflowed == false) { visible = current; } isHiddenChanged |= Panel.GetIsHidden(child) != isOverflowed; hasHiddenChildren |= isOverflowed; Panel.SetIsHidden(child, isOverflowed); } if (visibleChildrenCount > 0) { visible = visible - spacing; } Panel.HasHiddenChildren = hasHiddenChildren; // Stretch Pinned if (visible.IsGreaterThan(availableOriented.Direct) && stretchOverflow.Count > 0) { var currentPinStretch = stretchOverflow.Actual; var pinStretchTarget = (currentPinStretch - (visible - availableOriented.Direct)).Clamp(0, availableOriented.Direct); var pinStretchDesired = stretchOverflow.Stretch(FlexStretch.Fill, pinStretchTarget, FlexDistributor.Equalizer); if (pinStretchDesired < currentPinStretch) { var pinStretchIndex = 0; for (var index = 0; index < childrenCount; index++) { var flexElement = FlexElements[index]; if (CanPinStretch(flexElement)) { FlexElements[index] = stretchOverflow[pinStretchIndex++].WithStretchDirection(FlexStretchDirection.Shrink).WithMaxLength(flexElement.ActualLength); } else { FlexElements[index] = FlexElements[index].WithStretchDirection(FlexStretchDirection.Shrink).WithShrinkPriority(short.MaxValue); } } FinalMeasureItems(availableOriented, spacing, true); return(OrientedSize.Create(orientation, availableOriented.Direct, desiredOriented.Indirect)); } } } finally { FlexElementCollection.Release(stretchOverflow); } return(OrientedSize.Create(orientation, visible.Clamp(0, availableOriented.Direct), desiredOriented.Indirect)); } for (var index = 0; index < childrenCount; index++) { var flexElement = FlexElements[index]; var child = Panel.Elements[index]; if (child.Visibility == Visibility.Collapsed) { continue; } visibleChildrenCount++; current += flexElement.ActualLength; current += spacing; Panel.SetIsHidden(Panel.Elements[index], false); } Panel.HasHiddenChildren = false; visible = Math.Max(0, current); if (visibleChildrenCount > 0) { visible = visible - spacing; } return(OrientedSize.Create(orientation, visible.Clamp(0, availableOriented.Direct), desiredOriented.Indirect)); }
private Size ArrangeCoreImpl(Size finalSize) { var flexPanel = Panel; var flexPanelEx = Panel as IFlexPanelEx; var allowMeasure = flexPanelEx?.AllowMeasureInArrange ?? false; var orientation = flexPanel.Orientation; var useLayoutRounding = flexPanel.UseLayoutRounding; var spacing = GetRoundSpacing(flexPanel.Spacing, useLayoutRounding); for (var index = 0; index < flexPanel.Elements.Count; index++) { FlexElements[index] = FlexElements[index].WithUIElement(flexPanel.Elements[index], orientation); } var currentFlexElements = FlexElementCollection.Mount(FlexElements.Capacity); try { FlexElements.CopyTo(currentFlexElements); while (true) { var nextArrangePass = false; var size = new OrientedSize(orientation); var spacingDelta = 0.0; var finalOriented = finalSize.AsOriented(orientation); var finalIndirect = finalOriented.Indirect; var currentPoint = new OrientedPoint(orientation); var childFinalOriented = new OrientedSize(orientation); // Stretch Stretch(currentFlexElements, spacing, finalOriented.Direct, true); for (var index = 0; index < flexPanel.Elements.Count; index++) { var child = flexPanel.Elements[index]; var flexElement = currentFlexElements[index]; if (child.Visibility == Visibility.Collapsed) { continue; } if (flexPanel.GetIsHidden(child)) { child.Arrange(XamlConstants.ZeroRect); flexElement.ActualLength = 0.0; currentFlexElements[index] = flexElement; continue; } var desiredOriented = child.DesiredSize.AsOriented(orientation); childFinalOriented.Direct = flexElement.ActualLength; childFinalOriented.Indirect = Math.Max(finalIndirect, desiredOriented.Indirect); // Arrange Child var rect = new Rect(XamlConstants.ZeroPoint, childFinalOriented.Size).Offset(currentPoint); if (useLayoutRounding) { rect = rect.LayoutRound(RoundingMode.MidPointFromZero); } if (_measureInfinite && allowMeasure && desiredOriented.Direct.IsGreaterThan(childFinalOriented.Direct)) { var remeasureOriented = desiredOriented; remeasureOriented.ChangeDirect(childFinalOriented.Direct); child.Measure(remeasureOriented.Size); } child.Arrange(rect); var arrangeSize = GetActualArrangeSize(child); if (arrangeSize.IsEmpty == false) { rect.Width = arrangeSize.Width; rect.Height = arrangeSize.Height; } var finalChildDirect = rect.Size().AsOriented(orientation).Direct; if (IsArrangeFixed(flexElement) == false && finalChildDirect.IsLessThan(childFinalOriented.Direct)) { var length = finalChildDirect; flexElement.SetLengths(length, length, length, length); currentFlexElements[index] = flexElement; nextArrangePass = true; break; } if (useLayoutRounding) { var rectSize = rect.Size().AsOriented(orientation); flexElement.ActualLength = rectSize.Direct; currentPoint.Direct = Math.Max(0, (currentPoint.Direct + rectSize.Direct + spacing).LayoutRound(orientation, RoundingMode.MidPointFromZero)); } else { var rectSize = rect.Size().AsOriented(orientation); flexElement.ActualLength = rectSize.Direct; currentPoint.Direct = Math.Max(0, currentPoint.Direct + rectSize.Direct + spacing); } currentFlexElements[index] = flexElement; spacingDelta += spacing; size = size.StackSize(childFinalOriented); } if (nextArrangePass) { continue; } if (spacingDelta.Equals(0.0) == false) { size.Direct = Math.Max(0, size.Direct + spacingDelta - spacing); } var result = finalSize; if (orientation == Orientation.Horizontal) { result.Width = flexPanel.ShouldFill(Orientation.Horizontal) ? finalSize.Width : Math.Min(finalSize.Width, size.Width); } else { result.Height = flexPanel.ShouldFill(Orientation.Vertical) ? finalSize.Height : Math.Min(finalSize.Height, size.Height); } return(result); } } finally { FlexElementCollection.Release(currentFlexElements); } }