/// <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); }
/// <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); }