diff --git a/samples/ControlCatalog.Android/Resources/values/styles.xml b/samples/ControlCatalog.Android/Resources/values/styles.xml
index 49e079a719..3e1270256d 100644
--- a/samples/ControlCatalog.Android/Resources/values/styles.xml
+++ b/samples/ControlCatalog.Android/Resources/values/styles.xml
@@ -4,7 +4,7 @@
-
diff --git a/samples/ControlCatalog.Browser/app.css b/samples/ControlCatalog.Browser/app.css
index 27680f6e1a..0e6ab12461 100644
--- a/samples/ControlCatalog.Browser/app.css
+++ b/samples/ControlCatalog.Browser/app.css
@@ -1,4 +1,11 @@
-#out {
+:root {
+ --sat: env(safe-area-inset-top);
+ --sar: env(safe-area-inset-right);
+ --sab: env(safe-area-inset-bottom);
+ --sal: env(safe-area-inset-left);
+}
+
+#out {
height: 100vh;
width: 100vw
}
diff --git a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
index 74d5b2fd8c..b4dac5399c 100644
--- a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
+++ b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
@@ -3,7 +3,7 @@
Exe
manual
net6.0-ios
- 10.0
+ 13.0
True
iossimulator-x64
@@ -16,4 +16,4 @@
-
\ No newline at end of file
+
diff --git a/samples/ControlCatalog.iOS/Info.plist b/samples/ControlCatalog.iOS/Info.plist
index 6ffe3ba662..1dd4416c28 100644
--- a/samples/ControlCatalog.iOS/Info.plist
+++ b/samples/ControlCatalog.iOS/Info.plist
@@ -13,7 +13,7 @@
LSRequiresIPhoneOS
MinimumOSVersion
- 10.0
+ 13.0
UIDeviceFamily
1
@@ -39,9 +39,5 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIStatusBarHidden
-
- UIViewControllerBasedStatusBarAppearance
-
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index d71d51f068..246fe4385f 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -44,11 +44,11 @@ namespace ControlCatalog
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
- desktopLifetime.MainWindow = new MainWindow();
+ desktopLifetime.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() };
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
{
- singleViewLifetime.MainView = new MainView();
+ singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
}
base.OnFrameworkInitializationCompleted();
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 6f31d22677..9c439c874f 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@@ -12,6 +13,7 @@ using Avalonia.VisualTree;
using Avalonia.Styling;
using ControlCatalog.Models;
using ControlCatalog.Pages;
+using ControlCatalog.ViewModels;
namespace ControlCatalog
{
@@ -99,13 +101,47 @@ namespace ControlCatalog
};
}
+ internal MainWindowViewModel ViewModel => (MainWindowViewModel)DataContext!;
+
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
+
var decorations = this.Get("Decorations");
if (VisualRoot is Window window)
decorations.SelectedIndex = (int)window.SystemDecorations;
-
+
+ var insets = TopLevel.GetTopLevel(this)!.InsetsManager;
+ if (insets != null)
+ {
+ // In real life application these events should be unsubscribed to avoid memory leaks.
+ ViewModel.SafeAreaPadding = insets.SafeAreaPadding;
+ insets.SafeAreaChanged += (sender, args) =>
+ {
+ ViewModel.SafeAreaPadding = insets.SafeAreaPadding;
+ };
+
+ ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge;
+ ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
+
+ ViewModel.PropertyChanged += async (sender, args) =>
+ {
+ if (args.PropertyName == nameof(ViewModel.DisplayEdgeToEdge))
+ {
+ insets.DisplayEdgeToEdge = ViewModel.DisplayEdgeToEdge;
+ }
+ else if (args.PropertyName == nameof(ViewModel.IsSystemBarVisible))
+ {
+ insets.IsSystemBarVisible = ViewModel.IsSystemBarVisible;
+ }
+
+ // Give the OS some time to apply new values and refresh the view model.
+ await Task.Delay(100);
+ ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge;
+ ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
+ };
+ }
+
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
}
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index c589f41442..10ff94d25c 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -17,7 +17,6 @@ namespace ControlCatalog
{
this.InitializeComponent();
- DataContext = new MainWindowViewModel();
_recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
}
diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
index d690058b27..bcc1a71243 100644
--- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
+++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
@@ -5,11 +5,21 @@
xmlns:viewModels="using:ControlCatalog.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.WindowCustomizationsPage"
- x:DataType="viewModels:MainWindowViewModel">
-
-
-
-
-
+ x:DataType="viewModels:MainWindowViewModel"
+ x:CompileBindings="True">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
index 3628a9b8a7..8c6f0a2bd6 100644
--- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
@@ -6,6 +6,7 @@ using Avalonia.Platform;
using Avalonia.Reactive;
using System;
using System.ComponentModel.DataAnnotations;
+using Avalonia;
using MiniMvvm;
namespace ControlCatalog.ViewModels
@@ -20,6 +21,9 @@ namespace ControlCatalog.ViewModels
private bool _systemTitleBarEnabled;
private bool _preferSystemChromeEnabled;
private double _titleBarHeight;
+ private bool _isSystemBarVisible;
+ private bool _displayEdgeToEdge;
+ private Thickness _safeAreaPadding;
public MainWindowViewModel()
{
@@ -78,25 +82,25 @@ namespace ControlCatalog.ViewModels
{
get { return _chromeHints; }
set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
- }
+ }
public bool ExtendClientAreaEnabled
{
get { return _extendClientAreaEnabled; }
set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
- }
+ }
public bool SystemTitleBarEnabled
{
get { return _systemTitleBarEnabled; }
set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
- }
+ }
public bool PreferSystemChromeEnabled
{
get { return _preferSystemChromeEnabled; }
set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
- }
+ }
public double TitleBarHeight
{
@@ -122,6 +126,24 @@ namespace ControlCatalog.ViewModels
set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); }
}
+ public bool IsSystemBarVisible
+ {
+ get { return _isSystemBarVisible; }
+ set { this.RaiseAndSetIfChanged(ref _isSystemBarVisible, value); }
+ }
+
+ public bool DisplayEdgeToEdge
+ {
+ get { return _displayEdgeToEdge; }
+ set { this.RaiseAndSetIfChanged(ref _displayEdgeToEdge, value); }
+ }
+
+ public Thickness SafeAreaPadding
+ {
+ get { return _safeAreaPadding; }
+ set { this.RaiseAndSetIfChanged(ref _safeAreaPadding, value); }
+ }
+
public MiniCommand AboutCommand { get; }
public MiniCommand ExitCommand { get; }
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml
index 9e49298829..ed23797ad7 100644
--- a/samples/IntegrationTestApp/ShowWindowTest.axaml
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml
@@ -8,27 +8,27 @@
-
-
-
+
-
+
-
+
-
+
-
+
Normal
Minimized
Maximized
@@ -36,7 +36,7 @@
-
+
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
index ffcd6fc32c..f0be34fdaa 100644
--- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
@@ -35,11 +35,11 @@ namespace IntegrationTestApp
{
InitializeComponent();
DataContext = this;
- PositionChanged += (s, e) => this.GetControl("Position").Text = $"{Position}";
+ PositionChanged += (s, e) => this.GetControl("CurrentPosition").Text = $"{Position}";
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- _orderTextBox = this.GetControl("Order");
+ _orderTextBox = this.GetControl("CurrentOrder");
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
_timer.Tick += TimerOnTick;
_timer.Start();
@@ -55,13 +55,13 @@ namespace IntegrationTestApp
{
base.OnOpened(e);
var scaling = PlatformImpl!.DesktopScaling;
- this.GetControl("Position").Text = $"{Position}";
- this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
- this.GetControl("Scaling").Text = $"{scaling}";
+ this.GetControl("CurrentPosition").Text = $"{Position}";
+ this.GetControl("CurrentScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
+ this.GetControl("CurrentScaling").Text = $"{scaling}";
if (Owner is not null)
{
- var ownerRect = this.GetControl("OwnerRect");
+ var ownerRect = this.GetControl("CurrentOwnerRect");
var owner = (Window)Owner;
ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
}
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
index 247008c503..eb4b6bf6a0 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using Android.App;
using Android.Content;
using Android.Content.PM;
@@ -32,6 +33,9 @@ namespace Avalonia.Android
lifetime.View = View;
}
+ Window?.ClearFlags(WindowManagerFlags.TranslucentStatus);
+ Window?.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
+
base.OnCreate(savedInstanceState);
SetContentView(View);
@@ -55,6 +59,17 @@ namespace Avalonia.Android
}
}
+ protected override void OnResume()
+ {
+ base.OnResume();
+
+ // Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ {
+ Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
+ }
+ }
+
public event EventHandler BackRequested;
public override void OnBackPressed()
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index 2a345a857c..27481a598e 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -8,6 +8,7 @@ using Avalonia.Android.Platform;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
+using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Rendering;
@@ -67,6 +68,11 @@ namespace Avalonia.Android
}
_root.Renderer.Start();
+
+ if (_view.TryGetFeature(out var insetsManager) == true)
+ {
+ (insetsManager as AndroidInsetsManager)?.ApplyStatusBarState();
+ }
}
else
{
diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
new file mode 100644
index 0000000000..35d1b06e6a
--- /dev/null
+++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using Android.OS;
+using Android.Views;
+using AndroidX.AppCompat.App;
+using AndroidX.Core.View;
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Platform;
+using static Avalonia.Controls.Platform.IInsetsManager;
+
+namespace Avalonia.Android.Platform
+{
+ internal class AndroidInsetsManager : Java.Lang.Object, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener
+ {
+ private readonly AvaloniaMainActivity _activity;
+ private readonly TopLevelImpl _topLevel;
+ private readonly InsetsAnimationCallback _callback;
+ private bool _displayEdgeToEdge;
+ private bool _usesLegacyLayouts;
+ private bool? _systemUiVisibility;
+ private SystemBarTheme? _statusBarTheme;
+ private bool? _isDefaultSystemBarLightTheme;
+
+ public event EventHandler SafeAreaChanged;
+
+ public bool DisplayEdgeToEdge
+ {
+ get => _displayEdgeToEdge;
+ set
+ {
+ _displayEdgeToEdge = value;
+
+ if(Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ {
+ _activity.Window.Attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
+ }
+
+ WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
+ }
+ }
+
+ public AndroidInsetsManager(AvaloniaMainActivity activity, TopLevelImpl topLevel)
+ {
+ _activity = activity;
+ _topLevel = topLevel;
+ _callback = new InsetsAnimationCallback(WindowInsetsAnimationCompat.Callback.DispatchModeStop);
+
+ _callback.InsetsManager = this;
+
+ ViewCompat.SetOnApplyWindowInsetsListener(_activity.Window.DecorView, this);
+
+ ViewCompat.SetWindowInsetsAnimationCallback(_activity.Window.DecorView, _callback);
+
+ if(Build.VERSION.SdkInt < BuildVersionCodes.R)
+ {
+ _usesLegacyLayouts = true;
+ _activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
+ }
+
+ DisplayEdgeToEdge = false;
+ }
+
+ public Thickness SafeAreaPadding
+ {
+ get
+ {
+ var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
+
+ if (insets != null)
+ {
+ var renderScaling = _topLevel.RenderScaling;
+
+ var inset = insets.GetInsets(
+ (DisplayEdgeToEdge ?
+ WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() |
+ WindowInsetsCompat.Type.DisplayCutout() :
+ 0) | WindowInsetsCompat.Type.Ime());
+ var navBarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars());
+ var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime());
+
+ return new Thickness(inset.Left / renderScaling,
+ inset.Top / renderScaling,
+ inset.Right / renderScaling,
+ (imeInset.Bottom > 0 && ((_usesLegacyLayouts && !DisplayEdgeToEdge) || !_usesLegacyLayouts) ?
+ imeInset.Bottom - navBarInset.Bottom :
+ inset.Bottom) / renderScaling);
+ }
+
+ return default;
+ }
+ }
+
+ public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
+ {
+ NotifySafeAreaChanged(SafeAreaPadding);
+ return insets;
+ }
+
+ private void NotifySafeAreaChanged(Thickness safeAreaPadding)
+ {
+ SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding));
+ }
+
+ public void OnGlobalLayout()
+ {
+ NotifySafeAreaChanged(SafeAreaPadding);
+ }
+
+ public SystemBarTheme? SystemBarTheme
+ {
+ get
+ {
+ try
+ {
+ var compat = new WindowInsetsControllerCompat(_activity.Window, _topLevel.View);
+
+ return compat.AppearanceLightStatusBars ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
+ }
+ catch (Exception _)
+ {
+ return Controls.Platform.SystemBarTheme.Light;
+ }
+ }
+ set
+ {
+ _statusBarTheme = value;
+
+ var isDefault = _statusBarTheme == null;
+
+ if (!_topLevel.View.IsShown)
+ {
+ return;
+ }
+
+ var compat = new WindowInsetsControllerCompat(_activity.Window, _topLevel.View);
+
+ if (_isDefaultSystemBarLightTheme == null)
+ {
+ _isDefaultSystemBarLightTheme = compat.AppearanceLightStatusBars;
+ }
+
+ if (value == null && _isDefaultSystemBarLightTheme != null)
+ {
+ value = (bool)_isDefaultSystemBarLightTheme ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
+ }
+
+ compat.AppearanceLightStatusBars = value == Controls.Platform.SystemBarTheme.Light;
+ compat.AppearanceLightNavigationBars = value == Controls.Platform.SystemBarTheme.Light;
+
+ AppCompatDelegate.DefaultNightMode = isDefault ? AppCompatDelegate.ModeNightFollowSystem : compat.AppearanceLightStatusBars ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
+ }
+ }
+
+ public bool? IsSystemBarVisible
+ {
+ get
+ {
+ if(_activity.Window == null)
+ {
+ return true;
+ }
+ var compat = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
+
+ return compat?.IsVisible(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars());
+ }
+ set
+ {
+ _systemUiVisibility = value;
+
+ if (!_topLevel.View.IsShown)
+ {
+ return;
+ }
+
+ var compat = WindowCompat.GetInsetsController(_activity.Window, _topLevel.View);
+
+ if (value == null || value.Value)
+ {
+ compat?.Show(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars());
+ }
+ else
+ {
+ compat?.Hide(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars());
+
+ if (compat != null)
+ {
+ compat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorShowTransientBarsBySwipe;
+ }
+ }
+ }
+ }
+
+ internal void ApplyStatusBarState()
+ {
+ IsSystemBarVisible = _systemUiVisibility;
+ SystemBarTheme = _statusBarTheme;
+ }
+
+ private class InsetsAnimationCallback : WindowInsetsAnimationCompat.Callback
+ {
+ public InsetsAnimationCallback(int dispatchMode) : base(dispatchMode)
+ {
+ }
+
+ public AndroidInsetsManager InsetsManager { get; set; }
+
+ public override WindowInsetsCompat OnProgress(WindowInsetsCompat insets, IList runningAnimations)
+ {
+ foreach (var anim in runningAnimations)
+ {
+ if ((anim.TypeMask & WindowInsetsCompat.Type.Ime()) != 0)
+ {
+ var renderScaling = InsetsManager._topLevel.RenderScaling;
+
+ var inset = insets.GetInsets((InsetsManager.DisplayEdgeToEdge ? WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() | WindowInsetsCompat.Type.DisplayCutout() : 0) | WindowInsetsCompat.Type.Ime());
+ var navBarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars());
+ var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime());
+
+
+ var bottomPadding = (imeInset.Bottom > 0 && !InsetsManager.DisplayEdgeToEdge ? imeInset.Bottom - navBarInset.Bottom : inset.Bottom);
+ bottomPadding = (int)(bottomPadding * anim.InterpolatedFraction);
+
+ var padding = new Thickness(inset.Left / renderScaling,
+ inset.Top / renderScaling,
+ inset.Right / renderScaling,
+ bottomPadding / renderScaling);
+ InsetsManager?.NotifySafeAreaChanged(padding);
+ break;
+ }
+ }
+ return insets;
+ }
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index e511ed9a8b..b8d80a50ff 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -3,9 +3,7 @@ using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Graphics;
-using Android.OS;
using Android.Runtime;
-using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.Specific;
@@ -24,11 +22,13 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
+using Java.Util;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
-using Java.Util;
+using Android.OS;
+using Android.Text;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@@ -43,6 +43,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager;
+ private readonly AndroidInsetsManager _insetsManager;
private ViewImpl _view;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
@@ -59,6 +60,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
+ if (avaloniaView.Context is AvaloniaMainActivity mainActivity)
+ {
+ _insetsManager = new AndroidInsetsManager(mainActivity, this);
+ }
+
_nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
_storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
@@ -70,21 +76,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IInputRoot InputRoot { get; private set; }
- public virtual Size ClientSize
- {
- get
- {
- AndroidRect rect = new AndroidRect();
- AndroidRect intersection = new AndroidRect();
-
- _view.GetWindowVisibleDisplayFrame(intersection);
- _view.GetGlobalVisibleRect(rect);
-
- intersection.Intersect(rect);
-
- return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling);
- }
- }
+ public virtual Size ClientSize => _view.Size.ToSize(RenderScaling);
public Size? FrameSize => null;
@@ -285,7 +277,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
- // TODO adjust status bar depending on full screen mode.
+ if(_insetsManager != null)
+ {
+ _insetsManager.SystemBarTheme = themeVariant switch
+ {
+ PlatformThemeVariant.Light => SystemBarTheme.Light,
+ PlatformThemeVariant.Dark => SystemBarTheme.Dark,
+ _ => null,
+ };
+ }
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
@@ -403,6 +403,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return _nativeControlHost;
}
+ if (featureType == typeof(IInsetsManager))
+ {
+ return _insetsManager;
+ }
+
return null;
}
}
diff --git a/src/Avalonia.Controls/Platform/IInsetsManager.cs b/src/Avalonia.Controls/Platform/IInsetsManager.cs
new file mode 100644
index 0000000000..6288142805
--- /dev/null
+++ b/src/Avalonia.Controls/Platform/IInsetsManager.cs
@@ -0,0 +1,55 @@
+using System;
+using Avalonia.Metadata;
+
+#nullable enable
+namespace Avalonia.Controls.Platform
+{
+ [Unstable]
+ [NotClientImplementable]
+ public interface IInsetsManager
+ {
+ ///
+ /// Gets or sets whether the system bars are visible.
+ ///
+ bool? IsSystemBarVisible { get; set; }
+
+ ///
+ /// Gets or sets whether the window draws edge to edge. behind any visibile system bars.
+ ///
+ bool DisplayEdgeToEdge { get; set; }
+
+ ///
+ /// Gets the current safe area padding.
+ ///
+ Thickness SafeAreaPadding { get; }
+
+ ///
+ /// Occurs when safe area for the current window changes.
+ ///
+ event EventHandler? SafeAreaChanged;
+ }
+
+ public class SafeAreaChangedArgs : EventArgs
+ {
+ public SafeAreaChangedArgs(Thickness safeArePadding)
+ {
+ SafeAreaPadding = safeArePadding;
+ }
+
+ ///
+ public Thickness SafeAreaPadding { get; }
+ }
+
+ public enum SystemBarTheme
+ {
+ ///
+ /// Light system bar theme, with light background and a dark foreground
+ ///
+ Light,
+
+ ///
+ /// Bark system bar theme, with dark background and a light foreground
+ ///
+ Dark
+ }
+}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index fdcb8cc537..5c2a8c8a13 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -15,6 +15,7 @@ using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
+using Avalonia.Reactive;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
@@ -391,7 +392,9 @@ namespace Avalonia.Controls
??= AvaloniaLocator.Current.GetService()?.CreateProvider(this)
?? PlatformImpl?.TryGetFeature()
?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
-
+
+ public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature();
+
///
Point IRenderRoot.PointToClient(PixelPoint p)
{
diff --git a/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs b/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
new file mode 100644
index 0000000000..30f80ba27c
--- /dev/null
+++ b/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Browser.Interop;
+using Avalonia.Controls.Platform;
+using static Avalonia.Controls.Platform.IInsetsManager;
+
+namespace Avalonia.Browser
+{
+ internal class BrowserInsetsManager : IInsetsManager
+ {
+ public bool? IsSystemBarVisible
+ {
+ get
+ {
+ return DomHelper.IsFullscreen();
+ }
+ set
+ {
+ DomHelper.SetFullscreen(!value ?? false);
+ }
+ }
+
+ public bool DisplayEdgeToEdge { get; set; }
+
+ public event EventHandler? SafeAreaChanged;
+
+ public Thickness SafeAreaPadding
+ {
+ get
+ {
+ var padding = DomHelper.GetSafeAreaPadding();
+
+ return new Thickness(padding[0], padding[1], padding[2], padding[3]);
+ }
+ }
+
+ public void NotifySafeAreaPaddingChanged()
+ {
+ SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
+ }
+ }
+}
diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
index 1bf4636f61..7c5418dbeb 100644
--- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
+++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
@@ -31,6 +31,7 @@ namespace Avalonia.Browser
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager;
+ private readonly IInsetsManager? _insetsManager;
public BrowserTopLevelImpl(AvaloniaView avaloniaView)
{
@@ -40,9 +41,12 @@ namespace Avalonia.Browser
AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
_touchDevice = new TouchDevice();
_penDevice = new PenDevice();
+
+ _insetsManager = new BrowserInsetsManager();
_nativeControlHost = _avaloniaView.GetNativeControlHostImpl();
_storageProvider = new BrowserStorageProvider();
_systemNavigationManager = new BrowserSystemNavigationManagerImpl();
+
}
public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
@@ -69,6 +73,8 @@ namespace Avalonia.Browser
}
Resized?.Invoke(newSize, PlatformResizeReason.User);
+
+ (_insetsManager as BrowserInsetsManager)?.NotifySafeAreaPaddingChanged();
}
}
@@ -271,6 +277,11 @@ namespace Avalonia.Browser
return _nativeControlHost;
}
+ if (featureType == typeof(IInsetsManager))
+ {
+ return _insetsManager;
+ }
+
return null;
}
}
diff --git a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs
index d1133b8916..c1811bb117 100644
--- a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs
+++ b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs
@@ -11,6 +11,15 @@ internal static partial class DomHelper
[JSImport("AvaloniaDOM.createAvaloniaHost", AvaloniaModule.MainModuleName)]
public static partial JSObject CreateAvaloniaHost(JSObject element);
+ [JSImport("AvaloniaDOM.isFullscreen", AvaloniaModule.MainModuleName)]
+ public static partial bool IsFullscreen();
+
+ [JSImport("AvaloniaDOM.setFullscreen", AvaloniaModule.MainModuleName)]
+ public static partial JSObject SetFullscreen(bool isFullscreen);
+
+ [JSImport("AvaloniaDOM.getSafeAreaPadding", AvaloniaModule.MainModuleName)]
+ public static partial byte[] GetSafeAreaPadding();
+
[JSImport("AvaloniaDOM.addClass", AvaloniaModule.MainModuleName)]
public static partial void AddCssClass(JSObject element, string className);
diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts
index b99f8e7907..d9790f69e9 100644
--- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts
+++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts
@@ -84,4 +84,26 @@ export class AvaloniaDOM {
inputElement
};
}
+
+ public static isFullscreen(): boolean {
+ return document.fullscreenElement != null;
+ }
+
+ public static async setFullscreen(isFullscreen: boolean) {
+ if (isFullscreen) {
+ const doc = document.documentElement;
+ await doc.requestFullscreen();
+ } else {
+ await document.exitFullscreen();
+ }
+ }
+
+ public static getSafeAreaPadding(): number[] {
+ const top = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sat"));
+ const bottom = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sab"));
+ const left = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sal"));
+ const right = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sar"));
+
+ return [left, top, bottom, right];
+ }
}
diff --git a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
index b605d82541..18ccbad692 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
@@ -40,10 +40,12 @@ namespace Avalonia.iOS
var view = new AvaloniaView();
lifetime.View = view;
- Window.RootViewController = new UIViewController
+ var controller = new DefaultAvaloniaViewController
{
View = view
};
+ Window.RootViewController = controller;
+ view.InitWithController(controller);
});
builder.SetupWithLifetime(lifetime);
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 2d6b93f818..09721ad181 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -16,6 +16,7 @@ using Foundation;
using ObjCRuntime;
using OpenGLES;
using UIKit;
+using IInsetsManager = Avalonia.Controls.Platform.IInsetsManager;
namespace Avalonia.iOS
{
@@ -26,6 +27,7 @@ namespace Avalonia.iOS
private EmbeddableControlRoot _topLevel;
private TouchHandler _touches;
private ITextInputMethodClient _client;
+ private IAvaloniaViewController _controller;
public AvaloniaView()
{
@@ -48,10 +50,13 @@ namespace Avalonia.iOS
MultipleTouchEnabled = true;
}
+ ///
public override bool CanBecomeFirstResponder => true;
+ ///
public override bool CanResignFirstResponder => true;
+ ///
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
@@ -60,6 +65,7 @@ namespace Avalonia.iOS
settings?.TraitCollectionDidChange();
}
+ ///
public override void TintColorDidChange()
{
base.TintColorDidChange();
@@ -68,18 +74,31 @@ namespace Avalonia.iOS
settings?.TraitCollectionDidChange();
}
+ public void InitWithController(TController controller)
+ where TController : UIViewController, IAvaloniaViewController
+ {
+ _controller = controller;
+ _topLevelImpl._insetsManager.InitWithController(controller);
+ }
+
internal class TopLevelImpl : ITopLevelImpl
{
private readonly AvaloniaView _view;
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
+ internal readonly InsetsManager _insetsManager;
public AvaloniaView View => _view;
public TopLevelImpl(AvaloniaView view)
{
_view = view;
- _nativeControlHost = new NativeControlHostImpl(_view);
+ _nativeControlHost = new NativeControlHostImpl(view);
_storageProvider = new IOSStorageProvider(view);
+ _insetsManager = new InsetsManager(view);
+ _insetsManager.DisplayEdgeToEdgeChanged += (sender, b) =>
+ {
+ view._topLevel.Padding = b ? default : _insetsManager.SafeAreaPadding;
+ };
}
public void Dispose()
@@ -141,17 +160,14 @@ namespace Avalonia.iOS
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
// TODO adjust status bar depending on full screen mode.
- if (OperatingSystem.IsIOSVersionAtLeast(13))
+ if (OperatingSystem.IsIOSVersionAtLeast(13) && _view._controller is not null)
{
- var uiStatusBarStyle = themeVariant switch
+ _view._controller.PreferredStatusBarStyle = themeVariant switch
{
PlatformThemeVariant.Light => UIStatusBarStyle.DarkContent,
PlatformThemeVariant.Dark => UIStatusBarStyle.LightContent,
- _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null)
+ _ => UIStatusBarStyle.Default
};
-
- // Consider using UIViewController.PreferredStatusBarStyle in the future.
- UIApplication.SharedApplication.SetStatusBarStyle(uiStatusBarStyle, true);
}
}
@@ -175,6 +191,11 @@ namespace Avalonia.iOS
return _nativeControlHost;
}
+ if (featureType == typeof(IInsetsManager))
+ {
+ return _insetsManager;
+ }
+
return null;
}
}
diff --git a/src/iOS/Avalonia.iOS/InsetsManager.cs b/src/iOS/Avalonia.iOS/InsetsManager.cs
new file mode 100644
index 0000000000..62e560ddf9
--- /dev/null
+++ b/src/iOS/Avalonia.iOS/InsetsManager.cs
@@ -0,0 +1,83 @@
+using System;
+using Avalonia.Controls.Platform;
+using UIKit;
+
+namespace Avalonia.iOS;
+#nullable enable
+
+internal class InsetsManager : IInsetsManager
+{
+ private readonly AvaloniaView _view;
+ private IAvaloniaViewController? _controller;
+ private bool _displayEdgeToEdge;
+
+ public InsetsManager(AvaloniaView view)
+ {
+ _view = view;
+ }
+
+ internal void InitWithController(IAvaloniaViewController controller)
+ {
+ _controller = controller;
+ if (_controller is not null)
+ {
+ _controller.SafeAreaPaddingChanged += (_, _) =>
+ {
+ SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
+ DisplayEdgeToEdgeChanged?.Invoke(this, _displayEdgeToEdge);
+ };
+ }
+ }
+
+ public SystemBarTheme? SystemBarTheme
+ {
+ get => _controller?.PreferredStatusBarStyle switch
+ {
+ UIStatusBarStyle.LightContent => Controls.Platform.SystemBarTheme.Dark,
+ UIStatusBarStyle.DarkContent => Controls.Platform.SystemBarTheme.Light,
+ _ => null
+ };
+ set
+ {
+ if (_controller != null)
+ {
+ _controller.PreferredStatusBarStyle = value switch
+ {
+ Controls.Platform.SystemBarTheme.Light => UIStatusBarStyle.DarkContent,
+ Controls.Platform.SystemBarTheme.Dark => UIStatusBarStyle.LightContent,
+ null => UIStatusBarStyle.Default,
+ _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
+ };
+ }
+ }
+ }
+
+ public bool? IsSystemBarVisible
+ {
+ get => _controller?.PrefersStatusBarHidden == false;
+ set
+ {
+ if (_controller is not null)
+ {
+ _controller.PrefersStatusBarHidden = value == false;
+ }
+ }
+ }
+ public event EventHandler? SafeAreaChanged;
+ public event EventHandler? DisplayEdgeToEdgeChanged;
+
+ public bool DisplayEdgeToEdge
+ {
+ get => _displayEdgeToEdge;
+ set
+ {
+ if (_displayEdgeToEdge != value)
+ {
+ _displayEdgeToEdge = value;
+ DisplayEdgeToEdgeChanged?.Invoke(this, value);
+ }
+ }
+ }
+
+ public Thickness SafeAreaPadding => _controller?.SafeAreaPadding ?? default;
+}
diff --git a/src/iOS/Avalonia.iOS/ViewController.cs b/src/iOS/Avalonia.iOS/ViewController.cs
new file mode 100644
index 0000000000..42a0949a9c
--- /dev/null
+++ b/src/iOS/Avalonia.iOS/ViewController.cs
@@ -0,0 +1,74 @@
+using System;
+using Avalonia.Metadata;
+using UIKit;
+
+namespace Avalonia.iOS;
+
+[Unstable]
+public interface IAvaloniaViewController
+{
+ UIStatusBarStyle PreferredStatusBarStyle { get; set; }
+ bool PrefersStatusBarHidden { get; set; }
+ Thickness SafeAreaPadding { get; }
+ event EventHandler SafeAreaPaddingChanged;
+}
+
+///
+public class DefaultAvaloniaViewController : UIViewController, IAvaloniaViewController
+{
+ private UIStatusBarStyle? _preferredStatusBarStyle;
+ private bool? _prefersStatusBarHidden;
+
+ ///
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+ var size = View?.Frame.Size ?? default;
+ var frame = View?.SafeAreaLayoutGuide.LayoutFrame ?? default;
+ var safeArea = new Thickness(frame.Left, frame.Top, size.Width - frame.Right, size.Height - frame.Bottom);
+ if (SafeAreaPadding != safeArea)
+ {
+ SafeAreaPadding = safeArea;
+ SafeAreaPaddingChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ ///
+ public override bool PrefersStatusBarHidden()
+ {
+ return _prefersStatusBarHidden ??= base.PrefersStatusBarHidden();
+ }
+
+ ///
+ public override UIStatusBarStyle PreferredStatusBarStyle()
+ {
+ // don't set _preferredStatusBarStyle value if it's null, so we can keep "default" there instead of actual app style.
+ return _preferredStatusBarStyle ?? base.PreferredStatusBarStyle();
+ }
+
+ UIStatusBarStyle IAvaloniaViewController.PreferredStatusBarStyle
+ {
+ get => _preferredStatusBarStyle ?? UIStatusBarStyle.Default;
+ set
+ {
+ _preferredStatusBarStyle = value;
+ SetNeedsStatusBarAppearanceUpdate();
+ }
+ }
+
+ bool IAvaloniaViewController.PrefersStatusBarHidden
+ {
+ get => _prefersStatusBarHidden ?? false; // false is default on ios/ipados
+ set
+ {
+ _prefersStatusBarHidden = value;
+ SetNeedsStatusBarAppearanceUpdate();
+ }
+ }
+
+ ///
+ public Thickness SafeAreaPadding { get; private set; }
+
+ ///
+ public event EventHandler SafeAreaPaddingChanged;
+}
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
index fb3283fbe7..6b70edbdf4 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
@@ -93,7 +93,7 @@ namespace Avalonia.IntegrationTests.Appium
{
try
{
- _session.FindElementByAccessibilityId("WindowState").SendClick();
+ _session.FindElementByAccessibilityId("CurrentWindowState").SendClick();
_session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
// Wait for animations to run.
@@ -113,7 +113,7 @@ namespace Avalonia.IntegrationTests.Appium
{
using (OpenWindow(new Size(400, 400), ShowWindowMode.NonOwned, WindowStartupLocation.Manual))
{
- var windowState = _session.FindElementByAccessibilityId("WindowState");
+ var windowState = _session.FindElementByAccessibilityId("CurrentWindowState");
Assert.Equal("Normal", windowState.GetComboBoxValue());
@@ -170,7 +170,7 @@ namespace Avalonia.IntegrationTests.Appium
public void ShowMode(ShowWindowMode mode)
{
using var window = OpenWindow(null, mode, WindowStartupLocation.Manual);
- var windowState = _session.FindElementByAccessibilityId("WindowState");
+ var windowState = _session.FindElementByAccessibilityId("CurrentWindowState");
var original = GetWindowInfo();
Assert.Equal("Normal", windowState.GetComboBoxValue());
@@ -373,7 +373,7 @@ namespace Avalonia.IntegrationTests.Appium
{
PixelRect? ReadOwnerRect()
{
- var text = _session.FindElementByAccessibilityId("OwnerRect").Text;
+ var text = _session.FindElementByAccessibilityId("CurrentOwnerRect").Text;
return !string.IsNullOrWhiteSpace(text) ? PixelRect.Parse(text) : null;
}
@@ -384,13 +384,13 @@ namespace Avalonia.IntegrationTests.Appium
try
{
return new(
- Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text),
- Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text),
- PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text),
+ Size.Parse(_session.FindElementByAccessibilityId("CurrentClientSize").Text),
+ Size.Parse(_session.FindElementByAccessibilityId("CurrentFrameSize").Text),
+ PixelPoint.Parse(_session.FindElementByAccessibilityId("CurrentPosition").Text),
ReadOwnerRect(),
- PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text),
- double.Parse(_session.FindElementByAccessibilityId("Scaling").Text),
- Enum.Parse(_session.FindElementByAccessibilityId("WindowState").Text));
+ PixelRect.Parse(_session.FindElementByAccessibilityId("CurrentScreenRect").Text),
+ double.Parse(_session.FindElementByAccessibilityId("CurrentScaling").Text),
+ Enum.Parse(_session.FindElementByAccessibilityId("CurrentWindowState").Text));
}
catch (OpenQA.Selenium.NoSuchElementException) when (retry++ < 3)
{
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
index 90fdc2511f..039d30bbc1 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
@@ -393,7 +393,7 @@ namespace Avalonia.IntegrationTests.Appium
private int GetWindowOrder(string identifier)
{
var window = GetWindow(identifier);
- var order = window.FindElementByXPath("//*[@identifier='Order']");
+ var order = window.FindElementByXPath("//*[@identifier='CurrentOrder']");
return int.Parse(order.Text);
}