Пример #1
0
        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;
                }
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
 private void RestoreOrder(FlexElementCollection elements)
 {
     for (var i = 0; i < _currentCount; i++)
     {
         var item  = _sortedElements[i];
         var index = _sortedIndex[i];
         elements[index] = item;
     }
 }
Пример #4
0
        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();
                }
            }
        }
Пример #5
0
        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--;
        }
Пример #6
0
        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);
        }
Пример #7
0
        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;
                }
            }
        }
Пример #8
0
        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;
                }
            }
        }
Пример #9
0
        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;
                }
            }
        }
Пример #10
0
        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);
            }
        }
Пример #11
0
 private void ShrinkAll(FlexElementCollection elements, double target)
 {
     Distribute(elements, FlexDistributeDirection.Shrink, target);
 }
Пример #12
0
 private void ExpandAll(FlexElementCollection elements, double target)
 {
     Distribute(elements, FlexDistributeDirection.Expand, target);
 }
Пример #13
0
        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));
        }
Пример #14
0
        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);
            }
        }