Skip to content

Commit

Permalink
Merge pull request #18707 from unoplatform/dev/xygu/20241104/ios-nega…
Browse files Browse the repository at this point in the history
…tive-margin-clipping

fix(ios): negative margin clipped on device rotate
  • Loading branch information
Xiaoy312 authored Nov 6, 2024
2 parents a9b4f18 + 2a1c616 commit 46b7725
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Uno.UI.RuntimeTests.Extensions;
using System.Collections.ObjectModel;
using Windows.Foundation;
using Uno.UI.Helpers;
using Uno.UI.RuntimeTests.Extensions;
using Uno.UI.RuntimeTests.Helpers;

#if WINAPPSDK
using Uno.UI.Extensions;
#elif __IOS__
Expand Down Expand Up @@ -442,6 +445,44 @@ public async Task When_ItemsSource_INCC_StartTwo_RemoveOne()
await WindowHelper.WaitFor(() => Math.Abs(SUT.ActualHeight - 29) <= Epsilon, message: $"ListView failed to shrink from removing item: (ActualHeight: {SUT.ActualHeight})");
}

#if __IOS__ || __ANDROID__
[TestMethod]
[RunsOnUIThread]
public async Task When_Item_With_NegativeMargin_AsdAsd() // todo@xy: remove asdasd
{
const string PathData = "M 2 2 H 18 V 18 H 2 Z M 3 3 V 17 H 17 V 3 Z"; // 18x18 square with 1px border located at 2,2
var setup = new ListView
{
ItemsSource = Enumerable.Range(0, 1).ToArray(),
ItemTemplate = XamlHelper.LoadXaml<DataTemplate>($$"""
<DataTemplate>
<Grid Background="SkyBlue">
<Border x:Name="SutBorder" Background="Pink">
<PathIcon x:Name="SUT"
Data="{{PathData}}" Foreground="Red"
Width="20" Height="20"
Margin="0,-10,0,0"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</Grid>
</DataTemplate>
"""),
};

await UITestHelper.Load(setup);

// We can't really test rotating the device in the context of runtime tests. But still, this is a good repro.
// When loaded, we should see a red square, whose lower half is sitting in a pink background.
// When you rotate the device (portrait <-> landscape), the red square should still be fully visible.

// Previously, there is a bug where rotating could cause the view with negative padding to be clipped.
// The visual hierarchy needs a special setup, it is just the easiest to replicate with a ListView...

// It can be verified by checking `.Layer.Mask` remains consistent for the visual tree under ListView,
// before and after the device rotation. In this case, it was the SutBorder that magically had a `.Layer.Mask` assigned.
}
#endif

// Works around ScrollIntoView() not implemented for all platforms
private static void ScrollTo(ListViewBase listViewBase, double vOffset)
{
Expand Down
18 changes: 12 additions & 6 deletions src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,13 +427,19 @@ GradientBrush gradientBorder when gradientBorder.CanApplyToBorder(cornerRadius)
sublayers.Add(backgroundLayer);
parent.InsertSublayer(backgroundLayer, insertionIndex);

parent.Mask = new CAShapeLayer()
// Normally, the parent.Mask (or owner.Layer.Mask) is usually cleared by ApplyNativeClip that follows.
// However, on device rotation (portrait <-> landscape), ApplyNativeClip happens before this method, causing the view to have an unwanted clipping.
// Here, we are reusing the logics of ApplyNativeClip to decide when to not apply the mask (as it would be cleared anyways).
if (owner.GetNativeClippedViewport().IsFinite)
{
Path = outerPath,
Frame = area,
// We only use the fill color to create the mask area
FillColor = _Color.White.CGColor,
};
parent.Mask = new CAShapeLayer()
{
Path = outerPath,
Frame = area,
// We only use the fill color to create the mask area
FillColor = _Color.White.CGColor,
};
}

updatedBoundsPath = outerPath;
}
Expand Down
15 changes: 12 additions & 3 deletions src/Uno.UI/UI/Xaml/UIElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,16 @@ internal void ApplyClip()
#elif __WASM__
InvalidateArrange();
#else
var rect = GetNativeClippedViewport();

ApplyNativeClip(rect);
OnViewportUpdated(rect);
#endif
}

#if !(__SKIA__ || __WASM__)
internal Rect GetNativeClippedViewport()
{
Rect rect;

if (Clip == null)
Expand All @@ -957,10 +967,9 @@ internal void ApplyClip()
}
}

ApplyNativeClip(rect);
OnViewportUpdated(rect);
#endif
return rect;
}
#endif

partial void ApplyNativeClip(Rect rect
#if __SKIA__
Expand Down
7 changes: 1 addition & 6 deletions src/Uno.UI/UI/Xaml/UIElement.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ internal bool IsArrangeDirtyPath

partial void ApplyNativeClip(Rect rect)
{
if (rect.IsEmpty
|| double.IsPositiveInfinity(rect.X)
|| double.IsPositiveInfinity(rect.Y)
|| double.IsPositiveInfinity(rect.Width)
|| double.IsPositiveInfinity(rect.Height)
)
if (!rect.IsFinite)
{
if (!ClippingIsSetByCornerRadius)
{
Expand Down

0 comments on commit 46b7725

Please sign in to comment.