Skip to content

Commit

Permalink
fix(datepicker): Adjust picker flyout placement
Browse files Browse the repository at this point in the history
Fix #5554
  • Loading branch information
carldebilly committed May 6, 2021
1 parent acdc8f4 commit 7046ab0
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using NUnit.Framework;
using Uno.UITest;
using Uno.UITest.Helpers;
using Uno.UITest.Helpers.Queries;
using Uno.UITests.Helpers;
Expand Down Expand Up @@ -277,5 +279,54 @@ public void DatePickerFlyout_Date_Binding()

_app.WaitForNoElement(datePickerFlyout);
}

[Test]
[AutoRetry]
[TestCase("topLeft", true, false, true)]
[TestCase("topRight", true, false, true)]
[TestCase("bottomLeft", true, false, true)]
[TestCase("bottomRight", true, false, true)]
[TestCase("centerLeftOutside", false, true, true)]
[TestCase("rightLeftOutside", false, true, true)]
[TestCase("narrow", true, true, true)]

// Following cases are deactivated because of https://github.com/microsoft/microsoft-ui-xaml/issues/4968
//[TestCase("topCenterRotated", false, false, true)]
//[TestCase("center", true, true, true)]
//[TestCase("scaled", true, true, true)]
//[TestCase("viewBox", true, true, true)]
public void DatePicker_PickerFlyout_Placements(string name, bool checkHorizontal, bool checkVertical, bool checkWidth)
{
Run("UITests.Windows_UI_Xaml_Controls.DatePicker.DatePicker_Placement");
using var _ = new AssertionScope();

// Ensure picker not opened
_app.WaitForNoElement("HighlightRect");

var datePicker = _app.Marked(name);
datePicker.FastTap();

_app.WaitForElement("HighlightRect");

var controlRect = _app.GetLogicalRect(datePicker);
var highlightRect = _app.GetLogicalRect("HighlightRect");

if (checkHorizontal)
{
controlRect.CenterX.Should().BeApproximately(highlightRect.CenterX, 2f, "horizontal center");
}

if (checkVertical)
{
controlRect.CenterY.Should().BeApproximately(highlightRect.CenterY, 2f, "vertical center");
}

if (checkWidth)
{
controlRect.Width.Should().BeApproximately(highlightRect.Width, 2f, "width");
}

_app.FastTap("DismissButton");
}
}
}
7 changes: 7 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\DatePicker\DatePicker_Placement.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\DropDownButton\DropDownButtonPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -4878,6 +4882,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\DatePicker\DatePicker_Header.xaml.cs">
<DependentUpon>DatePicker_Header.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\DatePicker\DatePicker_Placement.xaml.cs">
<DependentUpon>DatePicker_Placement.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\DropDownButton\DropDownButtonPage.xaml.cs">
<DependentUpon>DropDownButtonPage.xaml</DependentUpon>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<Page
x:Class="UITests.Windows_UI_Xaml_Controls.DatePicker.DatePicker_Placement"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xamarin="http://uno.ui/xamarin"
mc:Ignorable="d xamarin"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid Padding="10">
<DatePicker x:Name="topLeft" VerticalAlignment="Top" HorizontalAlignment="Left" xamarin:UseNativeStyle="False" />
<DatePicker x:Name="topRight" VerticalAlignment="Top" HorizontalAlignment="Right" xamarin:UseNativeStyle="False" />
<DatePicker x:Name="bottomLeft" VerticalAlignment="Bottom" HorizontalAlignment="Left" xamarin:UseNativeStyle="False" />
<DatePicker x:Name="bottomRight" VerticalAlignment="Bottom" HorizontalAlignment="Right" xamarin:UseNativeStyle="False" />
<DatePicker x:Name="centerLeftOutside" VerticalAlignment="Center" HorizontalAlignment="Left" xamarin:UseNativeStyle="False">
<DatePicker.RenderTransform>
<TranslateTransform X="-100" />
</DatePicker.RenderTransform>
</DatePicker>
<DatePicker x:Name="rightLeftOutside" VerticalAlignment="Center" HorizontalAlignment="Right" xamarin:UseNativeStyle="False">
<DatePicker.RenderTransform>
<TranslateTransform X="100" />
</DatePicker.RenderTransform>
</DatePicker>
<DatePicker x:Name="topCenterRotated" VerticalAlignment="Top" HorizontalAlignment="Center" RenderTransformOrigin="0.5, 0.5" xamarin:UseNativeStyle="False">
<DatePicker.RenderTransform>
<RotateTransform Angle="90" />
</DatePicker.RenderTransform>
</DatePicker>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<DatePicker x:Name="center" RenderTransformOrigin="0.5, 0.5" xamarin:UseNativeStyle="False">
<DatePicker.RenderTransform>
<RotateTransform Angle="180" />
</DatePicker.RenderTransform>
</DatePicker>
<DatePicker x:Name="narrow" Width="120" xamarin:UseNativeStyle="False" />
<DatePicker x:Name="wide" Width="420" xamarin:UseNativeStyle="False" />
<DatePicker x:Name="scaled" RenderTransformOrigin="0.5, 0.5" xamarin:UseNativeStyle="False">
<DatePicker.RenderTransform>
<ScaleTransform ScaleX="1.25" ScaleY="0.75" />
</DatePicker.RenderTransform>
</DatePicker>
<Viewbox Height="50">
<DatePicker x:Name="viewBox" xamarin:UseNativeStyle="False" />
</Viewbox>
</StackPanel>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Windows.UI.Xaml.Controls;
using Uno.UI.Samples.Controls;

namespace UITests.Windows_UI_Xaml_Controls.DatePicker
{
[Sample]
public sealed partial class DatePicker_Placement : Page
{
public DatePicker_Placement()
{
this.InitializeComponent();
}
}
}
1 change: 0 additions & 1 deletion src/Uno.UI/UI/LayoutHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ internal static Size AtLeastZero(this Size value)
}
}

[Pure]
internal static Rect UnionWith(this Rect rect1, Rect rect2)
{
rect1.Union(rect2);
Expand Down
32 changes: 19 additions & 13 deletions src/Uno.UI/UI/Xaml/Controls/ComboBox/ComboBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,10 @@ public DropDownLayouter(ComboBox combo, PopupBase popup)
/// <inheritdoc />
public Size Measure(Size available, Size visibleSize)
{
if (!(Popup?.Child is FrameworkElement child) || Combo == null)
var popup = Popup;
var combo = Combo;

if (!(popup?.Child is FrameworkElement child) || combo == null)
{
return new Size();
}
Expand All @@ -487,7 +490,7 @@ public Size Measure(Size available, Size visibleSize)
// MaxWidth
// MaxHeight

if (Combo.IsPopupFullscreen)
if (combo.IsPopupFullscreen)
{
// Size : Note we set both Min and Max to match the UWP behavior which alter only those
// properties. The MinHeight is not set to allow the the root child control to specificy
Expand All @@ -500,15 +503,15 @@ public Size Measure(Size available, Size visibleSize)
{
// Set the popup child as max 9 x the height of the combo
// (UWP seams to actually limiting to 9 visible items ... which is not necessarily the 9 x the combo height)
var maxHeight = Math.Min(visibleSize.Height, Math.Min(Combo.MaxDropDownHeight, Combo.ActualHeight * _itemsToShow));
var maxHeight = Math.Min(visibleSize.Height, Math.Min(combo.MaxDropDownHeight, combo.ActualHeight * _itemsToShow));

child.MinHeight = Combo.ActualHeight;
child.MinWidth = Combo.ActualWidth;
child.MinHeight = combo.ActualHeight;
child.MinWidth = combo.ActualWidth;
child.MaxHeight = maxHeight;
child.MaxWidth = visibleSize.Width;

if (UsesManagedLayouting)
// This is a breaking change for Android/iOS in some specialised cases (see ComboBox_VisibleBounds sample), and
// This is a breaking change for Android/iOS in some specialized cases (see ComboBox_VisibleBounds sample), and
// since the layouting on those platforms is not yet as aligned with UWP as on WASM/Skia, and in particular
// virtualizing panels aren't used in the ComboBox yet (#556 and #1133), we skip it for now
{
Expand All @@ -527,12 +530,15 @@ public Size Measure(Size available, Size visibleSize)
/// <inheritdoc />
public void Arrange(Size finalSize, Rect visibleBounds, Size desiredSize, Point? upperLeftLocation)
{
if (!(Popup?.Child is FrameworkElement child) || Combo == null)
var popup = Popup;
var combo = Combo;

if (!(popup?.Child is FrameworkElement child) || combo == null)
{
return;
}

if (Combo.IsPopupFullscreen)
if (combo.IsPopupFullscreen)
{
Point getChildLocation()
{
Expand All @@ -559,7 +565,7 @@ Point getChildLocation()
return;
}

var comboRect = Combo.GetAbsoluteBoundsRect();
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
Expand All @@ -571,14 +577,14 @@ Point getChildLocation()
// 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;
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 placement = Uno.UI.Xaml.Controls.ComboBox.GetDropDownPreferredPlacement(combo);
var stickyThreshold = Math.Max(1, Math.Min(4, (itemsCount / 2) - 1));
switch (placement)
{
Expand All @@ -593,7 +599,7 @@ Point getChildLocation()
// 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):
&& (itemsCount <= _itemsToShow && frame.Height < (combo.ActualHeight * _itemsToShow) - 3):

frame.Y = comboRect.Bottom - frame.Height;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,6 @@ public IAsyncOperation<TResult> Start(FrameworkElement pTarget)

AssertInvariants();

//Cleanup:
//RRETURN(hr);

return spAsyncOp;
}

Expand Down
34 changes: 34 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.Pickers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Windows.Foundation;
using Windows.UI.ViewManagement;
using Uno.UI;
using NotImplementedException = System.NotImplementedException;

namespace Windows.UI.Xaml.Controls.Primitives
{
Expand All @@ -13,6 +16,37 @@ internal void PlaceFlyoutForDateTimePicker(Point point)

//m_isPositionedForDateTimePicker = true;

// **************************************************************************************
// UNO-FIX: Ensure the flyout stays in visible bounds
// **************************************************************************************
var childRect = ((FrameworkElement)_popup.Child).GetAbsoluteBoundsRect();
var rect = new Rect(point, childRect.Size);
var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;

if (rect.Right > visibleBounds.Right)
{
rect.X = visibleBounds.Right - rect.Width;
}

if (rect.Bottom > visibleBounds.Bottom)
{
rect.Y = visibleBounds.Bottom - rect.Height;
}

if (rect.Top < visibleBounds.Top)
{
rect.Y = visibleBounds.Top;
}

if (rect.Left < visibleBounds.Left)
{
rect.X = visibleBounds.Left;
}
// **************************************************************************************

//_popup.CustomLayouter = new PickerLayouter(this);
SetPopupPositionPartial(default, rect.Location);

//IFC_RETURN(ForwardPopupFlowDirection());
//SetTargetPosition(point);
//IFC_RETURN(ApplyTargetPosition());
Expand Down
7 changes: 7 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/Primitives/PickerFlyoutBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public partial class PickerFlyoutBase : FlyoutBase
protected virtual void OnConfirmed() => throw new InvalidOperationException();

protected virtual bool ShouldShowConfirmationButtons() => throw new InvalidOperationException();

protected override void InitializePopupPanel()
{
// -- UNO STUFF --
// Prevent Flyout from creating a PlacementPopupPanel and let the Popup create it.
// That way the FlyoutBase.PlaceFlyoutForDateTimePicker() will be able to do its job.
}
}

}

0 comments on commit 7046ab0

Please sign in to comment.