Esempio n. 1
0
            /// <inheritdoc />
            public void Arrange(Size finalSize, Rect visibleBounds, Size desiredSize)
            {
                if (!(_popup.Child is FrameworkElement child))
                {
                    return;
                }

                if (_combo.IsPopupFullscreen)
                {
                    child.Arrange(new Rect(new Point(), finalSize));

                    return;
                }

                var comboRect = _combo.GetAbsoluteBoundsRect();
                var frame     = new Rect(comboRect.Location, desiredSize.AtMost(visibleBounds.Size));

                // On windows, the popup is Y-aligned accordingly to the selected item in order to keep
                // the selected at the same place no matter if the drop down is open or not.
                // For instance if selected is:
                //  * the first option: The drop-down appears below the combobox
                //  * the last option: The dop-down appears above the combobox
                // However this would requires us to determine the actual location of the SelectedItem container's
                // which might not be ready at this point (we could try a 2-pass arrange), and to scroll into view to make it visible.
                // So for now we only rely on the SelectedIndex and make a highly improvable vertical alignment based on it.

                var itemsCount    = _combo.NumberOfItems;
                var selectedIndex = _combo.SelectedIndex;

                if (selectedIndex < 0 && itemsCount > 0)
                {
                    selectedIndex = itemsCount / 2;
                }

                var stickyThreshold = Math.Max(1, Math.Min(4, (itemsCount / 2) - 1));

                if (selectedIndex >= 0 && selectedIndex < stickyThreshold)
                {
                    // Try to appear below
                    frame.Y = comboRect.Top;
                }
                else if (selectedIndex >= 0 && selectedIndex >= itemsCount - stickyThreshold
                         // As we don't scroll into view to the selected item, this case seems awkward if the selected item
                         // is not directly visible (i.e. without scrolling) when the drop-down appears.
                         // So if we detect that we should had to scroll to make it visible, we don't try to appear above!
                         && (itemsCount <= _itemsToShow && frame.Height < (_combo.ActualHeight * _itemsToShow) - 3))
                {
                    // Try to appear above
                    frame.Y = comboRect.Bottom - frame.Height;
                }
                else
                {
                    // Try to appear centered
                    frame.Y = comboRect.Top - (frame.Height / 2.0) + (comboRect.Height / 2.0);
                }

                // Make sure that the popup does not appears out of the viewport
                if (frame.Left < visibleBounds.Left)
                {
                    frame.X = visibleBounds.X;
                }
                else if (frame.Right > visibleBounds.Width)
                {
                    // On UWP, the popup is just aligned to the right on the window if it overflows on right
                    // Note: frame.Width is already at most visibleBounds.Width
                    frame.X = visibleBounds.Width - frame.Width;
                }
                if (frame.Top < visibleBounds.Top)
                {
                    frame.Y = visibleBounds.Y;
                }
                else if (frame.Bottom > visibleBounds.Height)
                {
                    // On UWP, the popup always let 1 px free at the bottom
                    // Note: frame.Height is already at most visibleBounds.Height
                    frame.Y = visibleBounds.Height - frame.Height - 1;
                }

                if (this.Log().IsEnabled(LogLevel.Debug))
                {
                    this.Log().Debug($"Layout the combo's dropdown at {frame} (desired: {desiredSize} / available: {finalSize} / visible: {visibleBounds} / selected: {selectedIndex} of {itemsCount})");
                }

                child.Arrange(frame);
            }
Esempio n. 2
0
            /// <inheritdoc />
            public void Arrange(Size finalSize, Rect visibleBounds, Size desiredSize, Point?upperLeftLocation)
            {
                if (!(_popup.Child is FrameworkElement child))
                {
                    return;
                }

                if (_combo.IsPopupFullscreen)
                {
                    Point getChildLocation()
                    {
                        switch (child.VerticalAlignment)
                        {
                        default:
                        case VerticalAlignment.Top:
                            return(new Point());

                        case VerticalAlignment.Bottom:
                            return(new Point(0, finalSize.Height - child.DesiredSize.Height));
                        }
                    }

                    var childSize = new Size(finalSize.Width, Math.Min(finalSize.Height, child.DesiredSize.Height));
                    var finalRect = new Rect(getChildLocation(), childSize);

                    if (this.Log().IsEnabled(LogLevel.Debug))
                    {
                        this.Log().Debug($"FullScreen Layout for dropdown (desired: {desiredSize} / available: {finalSize} / visible: {visibleBounds} / finalRect: {finalRect} )");
                    }

                    child.Arrange(finalRect);

                    return;
                }

                var comboRect = _combo.GetAbsoluteBoundsRect();
                var frame     = new Rect(comboRect.Location, desiredSize.AtMost(visibleBounds.Size));

                // On windows, the popup is Y-aligned accordingly to the selected item in order to keep
                // the selected at the same place no matter if the drop down is open or not.
                // For instance if selected is:
                //  * the first option: The drop-down appears below the combobox
                //  * the last option: The dop-down appears above the combobox
                // However this would requires us to determine the actual location of the SelectedItem container's
                // which might not be ready at this point (we could try a 2-pass arrange), and to scroll into view to make it visible.
                // So for now we only rely on the SelectedIndex and make a highly improvable vertical alignment based on it.

                var itemsCount    = _combo.NumberOfItems;
                var selectedIndex = _combo.SelectedIndex;

                if (selectedIndex < 0 && itemsCount > 0)
                {
                    selectedIndex = itemsCount / 2;
                }

                var placement       = Uno.UI.Xaml.Controls.ComboBox.GetDropDownPreferredPlacement(_combo);
                var stickyThreshold = Math.Max(1, Math.Min(4, (itemsCount / 2) - 1));

                switch (placement)
                {
                case DropDownPlacement.Below:
                case DropDownPlacement.Auto when selectedIndex >= 0 && selectedIndex < stickyThreshold:
                    frame.Y = comboRect.Top;
                    break;

                case DropDownPlacement.Above:
                case DropDownPlacement.Auto when
                    selectedIndex >= 0 && selectedIndex >= itemsCount - stickyThreshold
                    // As we don't scroll into view to the selected item, this case seems awkward if the selected item
                    // is not directly visible (i.e. without scrolling) when the drop-down appears.
                    // So if we detect that we should had to scroll to make it visible, we don't try to appear above!
                    && (itemsCount <= _itemsToShow && frame.Height < (_combo.ActualHeight * _itemsToShow) - 3):

                    frame.Y = comboRect.Bottom - frame.Height;
                    break;

                case DropDownPlacement.Centered:
                case DropDownPlacement.Auto:                         // For now we don't support other alignments than top/bottom/center, but on UWP auto can also be 2/3 - 1/3
                default:
                    // Try to appear centered
                    frame.Y = comboRect.Top - (frame.Height / 2.0) + (comboRect.Height / 2.0);
                    break;
                }

                // Make sure that the popup does not appears out of the viewport
                if (frame.Left < visibleBounds.Left)
                {
                    frame.X = visibleBounds.X;
                }
                else if (frame.Right > visibleBounds.Width)
                {
                    // On UWP, the popup is just aligned to the right on the window if it overflows on right
                    // Note: frame.Width is already at most visibleBounds.Width
                    frame.X = visibleBounds.Width - frame.Width;
                }
                if (frame.Top < visibleBounds.Top)
                {
                    frame.Y = visibleBounds.Y;
                }
                else if (frame.Bottom > visibleBounds.Height)
                {
                    // On UWP, the popup always let 1 px free at the bottom
                    // Note: frame.Height is already at most visibleBounds.Height
                    frame.Y = visibleBounds.Height - frame.Height - 1;
                }

                if (this.Log().IsEnabled(LogLevel.Debug))
                {
                    this.Log().Debug($"Layout the combo's dropdown at {frame} (desired: {desiredSize} / available: {finalSize} / visible: {visibleBounds} / selected: {selectedIndex} of {itemsCount})");
                }

                if (upperLeftLocation is Point offset)
                {
                    // Compensate for origin location is some popup providers (Android
                    // is one, particularly when the status bar is translucent)
                    frame.X -= offset.X;
                    frame.Y -= offset.Y;
                }

                child.Arrange(frame);
            }