Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: dispatch safearea inset setter logic #567

Merged
merged 9 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions samples/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@
<PackageVersion Include="Uno.Extensions.Logging.WebAssembly.Console" Version="1.4.0" />
<PackageVersion Include="Uno.Material" Version="2.6.0" />
<PackageVersion Include="Uno.Material.WinUI" Version="2.6.0" />
<PackageVersion Include="Uno.UI" Version="4.7.37" />
<PackageVersion Include="Uno.UI" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.UI.Adapter.Microsoft.Extensions.Logging" Version="4.5.9" />
<PackageVersion Include="Uno.UI.RemoteControl" Version="4.7.37" />
<PackageVersion Include="Uno.UI.Skia.Gtk" Version="4.7.37" />
<PackageVersion Include="Uno.UI.Skia.Tizen" Version="4.7.37" />
<PackageVersion Include="Uno.UI.Skia.Wpf" Version="4.7.37" />
<PackageVersion Include="Uno.UI.WebAssembly" Version="4.7.37" />
<PackageVersion Include="Uno.UI.RemoteControl" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.UI.Skia.Gtk" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.UI.Skia.Tizen" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.UI.Skia.Wpf" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.UI.WebAssembly" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.UniversalImageLoader" Version="1.9.35" />
<PackageVersion Include="Uno.Wasm.Bootstrap" Version="7.0.3" />
<PackageVersion Include="Uno.Wasm.Bootstrap.DevServer" Version="7.0.3" />
<PackageVersion Include="Uno.WinUI" Version="4.7.37" />
<PackageVersion Include="Uno.WinUI.RemoteControl" Version="4.7.37" />
<PackageVersion Include="Uno.WinUI.Skia.Gtk" Version="4.7.37" />
<PackageVersion Include="Uno.WinUI.WebAssembly" Version="4.7.37" />
<PackageVersion Include="Uno.WinUI" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.WinUI.RemoteControl" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.WinUI.Skia.Gtk" Version="4.10.0-dev.85" />
<PackageVersion Include="Uno.WinUI.WebAssembly" Version="4.10.0-dev.85" />
<PackageVersion Include="Xamarin.Android.Support.CustomTabs" Version="28.0.0.3" />
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.4.0.2" />
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.3.1.1" />
Expand Down
172 changes: 172 additions & 0 deletions src/Uno.Toolkit.RuntimeTests/Tests/SafeAreaTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Uno.Disposables;
using Uno.Toolkit.RuntimeTests.Extensions;
using Uno.Toolkit.RuntimeTests.Helpers;
using Uno.Toolkit.RuntimeTests.Tests.TestPages;
using Uno.Toolkit.UI;
using Uno.UI.RuntimeTests;
using Windows.System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Uno.UI;
#if __IOS__
using UIKit;
#endif

#if IS_WINUI
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Windows.UI.ViewManagement;
#else
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI;
using Windows.UI.ViewManagement;
using Uno.UI.Xaml;
#endif

namespace Uno.Toolkit.RuntimeTests.Tests
{
[TestClass]
[RunsOnUIThread]
internal partial class SafeAreaTests
{
#if __ANDROID__
[TestMethod]
public async Task Translucent_SystemBars()
{
using var _ = SetupWindow();
using var __ = UseTranslucentBars();

var redGrid = new Grid
{
Background = new SolidColorBrush(Colors.Red),
};

var blueGrid = new Grid
{
Background = new SolidColorBrush(Colors.Blue),
};

redGrid.Children.Add(blueGrid);

SafeArea.SetInsets(redGrid, SafeArea.InsetMask.VisibleBounds);

await UnitTestUIContentHelperEx.SetContentAndWait(redGrid);

var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;
var blueRect = blueGrid.TransformToVisual(null).TransformBounds(new Windows.Foundation.Rect(0, 0, blueGrid.ActualWidth, blueGrid.ActualHeight));
var redRect = redGrid.TransformToVisual(null).TransformBounds(new Windows.Foundation.Rect(0, 0, redGrid.ActualWidth, redGrid.ActualHeight));

var statusBarHeight = visibleBounds.Top - redRect.Top;
var navAreaHeight = redRect.Bottom - visibleBounds.Bottom;

Assert.AreEqual(blueRect.Top, statusBarHeight);
Assert.AreEqual(blueRect.Bottom, redRect.Bottom - navAreaHeight);
Assert.AreEqual(redGrid.Padding.Top, statusBarHeight);
Assert.AreEqual(redGrid.Padding.Bottom, navAreaHeight);
}

[TestMethod]
public async Task Translucent_SystemBars_Dynamic()
{
using var _ = SetupWindow();

var redGrid = new Grid
{
Background = new SolidColorBrush(Colors.Red),
};

var blueGrid = new Grid
{
Background = new SolidColorBrush(Colors.Blue),
};

redGrid.Children.Add(blueGrid);
SafeArea.SetInsets(redGrid, SafeArea.InsetMask.VisibleBounds);

await UnitTestUIContentHelperEx.SetContentAndWait(redGrid);

var blueWithOpaqueBars = blueGrid.TransformToVisual(redGrid).TransformBounds(new Windows.Foundation.Rect(0, 0, blueGrid.ActualWidth, blueGrid.ActualHeight));
var redWithOpaqueBars = redGrid.TransformToVisual(redGrid).TransformBounds(new Windows.Foundation.Rect(0, 0, redGrid.ActualWidth, redGrid.ActualHeight));
var visibleBoundsWithOpaqueBars = ApplicationView.GetForCurrentView().VisibleBounds;

// before: redWithOpaqueBars should be at (0, [statusBarHeight]) and the same size as visibleBoundsWithOpaqueBars
using var __ = UseTranslucentBars();
kazo0 marked this conversation as resolved.
Show resolved Hide resolved
// after: redWithTranslucentBars should be at (0, 0) and should differ from visibleBoundsWithTranslucentBars in height by [statusBarHeight] + [navAreaHeight]

await UnitTestsUIContentHelper.WaitForIdle();

var blueWithTranslucentBars = blueGrid.TransformToVisual(redGrid).TransformBounds(new Windows.Foundation.Rect(0, 0, blueGrid.ActualWidth, blueGrid.ActualHeight));
var redWithTranslucentBars = redGrid.TransformToVisual(redGrid).TransformBounds(new Windows.Foundation.Rect(0, 0, redGrid.ActualWidth, redGrid.ActualHeight));
var visibleBoundsWithTranslucentBars = ApplicationView.GetForCurrentView().VisibleBounds;

var statusBarHeight = visibleBoundsWithTranslucentBars.Top - redWithTranslucentBars.Top;
var navAreaHeight = redWithTranslucentBars.Bottom - visibleBoundsWithTranslucentBars.Bottom;

Assert.AreEqual(blueWithTranslucentBars.Top, statusBarHeight, message: $"Blue rect top: {blueWithTranslucentBars.Top} should equal status bar height: {statusBarHeight}");
Assert.AreEqual(blueWithTranslucentBars.Bottom, redWithTranslucentBars.Bottom - navAreaHeight, message: $"Blue rect bottom: {blueWithTranslucentBars.Bottom} should be offset by nav area height: {navAreaHeight}");
Assert.AreEqual(redGrid.Padding.Top, statusBarHeight, message: $"Red rect padding top: {redGrid.Padding.Top} should be equal to status bar height: {statusBarHeight}");
Assert.AreEqual(redGrid.Padding.Bottom, navAreaHeight, message: $"Red rect padding bottom: {redGrid.Padding.Bottom} should be equal to nav area height: {navAreaHeight}");
}


private static IDisposable UseTranslucentBars()
{
var activity = Uno.UI.ContextHelper.Current as Android.App.Activity;
activity?.Window?.AddFlags(Android.Views.WindowManagerFlags.TranslucentNavigation | Android.Views.WindowManagerFlags.TranslucentStatus);

return Disposable.Create(() =>
{
activity?.Window?.ClearFlags(Android.Views.WindowManagerFlags.TranslucentNavigation | Android.Views.WindowManagerFlags.TranslucentStatus);
});
}


private static IDisposable SetupWindow()
{
var disposables = new CompositeDisposable();

ApplicationView.GetForCurrentView().ExitFullScreenMode();

// The [RequiresFullWindow] attribute sets the app to fullscreen on Android, hiding all system bars.
// This method maintains the fullscreen state, but shows the system bars for tests that need them.
UnitTestsUIContentHelper.UseActualWindowRoot = true;
UnitTestsUIContentHelper.SaveOriginalContent();

disposables.Add(Disposable.Create(() =>
{
UnitTestsUIContentHelper.RestoreOriginalContent();
UnitTestsUIContentHelper.UseActualWindowRoot = false;
}));

// When runtime tests are initiated through the UI Tests, each test starts by entering fullscreen.
// We need to exit fullscreen in order to test the status bar height.
// Also, the emulator running on the CI has Window flags (WindowManagerFlags.LayoutInScreen | WindowManagerFlags.LayoutInsetDecor)
// which causes issues when running tests on the UI thread while attempting to alter the window flags
if (ContextHelper.Current is Android.App.Activity activity
&& activity.Window is { } window
&& window.Attributes is { } attr)
{
var flags = attr.Flags;
window.ClearFlags(Android.Views.WindowManagerFlags.LayoutInScreen | Android.Views.WindowManagerFlags.LayoutInsetDecor);

disposables.Add(Disposable.Create(() => window.SetFlags(flags, flags)));
}

return disposables;
}
#endif
}
}
51 changes: 26 additions & 25 deletions src/Uno.Toolkit.UI/Controls/SafeArea/SafeArea.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,6 @@ private static Rect GetVisibleBounds(Rect? safeAreaOverride = null, bool withSof
// the InputRect to align with the VisibleBounds Rect.
if (totalOffset > 0)
{


var navBarOffset = (totalOffset - statusBarOffset);

inputRect.Height -= navBarOffset;
Expand Down Expand Up @@ -562,30 +560,33 @@ private void ApplyInsets(Thickness insets)
{
return;
}

if (_insetMode == InsetMode.Padding
&& !PaddingHelper.GetPadding(owner).Equals(insets)
&& PaddingHelper.SetPadding(owner, insets))
if (_insetMode == InsetMode.Padding &&
owner.TryUpdatePadding(insets))
{
_appliedPadding = insets;
LogApplyInsets();
OnInsetsApplied(owner, insets);
}
else if (_insetMode == InsetMode.Margin)
{
if (!owner.Margin.Equals(insets))
{
_appliedMargin = insets;
owner.Margin = insets;
LogApplyInsets();
OnInsetsApplied(owner, insets);
}
}
}

void LogApplyInsets()
private void OnInsetsApplied(FrameworkElement owner, Thickness newInsets)
{
#if __ANDROID__
// Dispatching on Android prevents issues where layout/render changes occurring
// during the initial loading of the view are not always properly picked up by the layouting/rendering engine.
owner.GetDispatcherCompat().Schedule(owner.InvalidateMeasure);
#endif
if (_log.IsEnabled(LogLevel.Debug))
{
if (_log.IsEnabled(LogLevel.Debug))
{
_log.LogDebug($"ApplyInsets={insets}, Mode={_insetMode}");
}
_log.LogDebug($"ApplyInsets={newInsets}, Mode={_insetMode}");
}
}

Expand Down Expand Up @@ -652,21 +653,21 @@ internal void OnInsetModeChanged(InsetMode oldValue, InsetMode newValue)

if (Owner is { } owner)
{
if (oldValue == InsetMode.Margin)
{
_appliedMargin = new Thickness(0);
owner.Margin = _originalMargin;
}
else if (oldValue == InsetMode.Padding)
{
_appliedPadding = new Thickness(0);
PaddingHelper.SetPadding(owner, _originalPadding);
}
if (oldValue == InsetMode.Margin)
{
_appliedMargin = new Thickness(0);
owner.Margin = _originalMargin;
OnInsetsApplied(owner, _originalMargin);
}
else if (oldValue == InsetMode.Padding)
{
_appliedPadding = new Thickness(0);
PaddingHelper.SetPadding(owner, _originalPadding);
OnInsetsApplied(owner, _originalPadding);
}
}

UpdateInsets();
}
}

}
}
22 changes: 12 additions & 10 deletions src/Uno.Toolkit.UI/Helpers/DispatcherCompat.cs
Xiaoy312 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ public void Invoke(Priority priority, _Handler handler)
}
else
{
#if IS_WINUI
_impl.TryEnqueue(RemapPriority(priority), handler);
#else
_ = _impl.RunAsync(RemapPriority(priority), handler);
#endif
Schedule(priority, handler);
}
}

Expand All @@ -80,11 +76,7 @@ public Task RunAsync(Priority priority, _Handler handler)
{
var tcs = new TaskCompletionSource<object>();

#if IS_WINUI
_impl.TryEnqueue(RemapPriority(priority), () =>
#else
_ = _impl.RunAsync(RemapPriority(priority), () =>
#endif
Schedule(priority, () =>
{
try
{
Expand All @@ -100,4 +92,14 @@ public Task RunAsync(Priority priority, _Handler handler)
return tcs.Task;
}
}

public void Schedule(_Handler handler) => Schedule(default, handler);
public void Schedule(Priority priority, _Handler handler)
{
#if IS_WINUI
_impl.TryEnqueue(RemapPriority(priority), handler);
#else
_ = _impl.RunAsync(RemapPriority(priority), handler);
#endif
}
}
21 changes: 21 additions & 0 deletions src/Uno.Toolkit.UI/Helpers/PaddingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ public static bool SetPadding(UIElement uiElement, Thickness padding)
return false;
}

public static bool TryUpdatePadding(this FrameworkElement frameworkElement, Thickness newPadding)
{
//Fast path
if (TryGetPadding(frameworkElement, out var padding))
{
return !padding.Equals(newPadding) && TrySetPadding(frameworkElement, newPadding);
}

var property = frameworkElement.FindDependencyPropertyUsingReflection<Thickness>("PaddingProperty");
if (property is { } && frameworkElement.GetValue(property) is Thickness currentPadding)
{
if (!currentPadding.Equals(newPadding))
{
frameworkElement.SetValue(property, newPadding);
return true;
}
}

return false;
}

internal static bool TryGetPadding(this FrameworkElement frameworkElement, out Thickness padding)
{
(var result, padding) = frameworkElement switch
Expand Down
Loading