committed by
GitHub
33 changed files with 826 additions and 88 deletions
@ -1,5 +1,12 @@ |
|||||
#!/usr/bin/env bash |
#!/usr/bin/env bash |
||||
|
|
||||
cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) |
cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) |
||||
dotnet restore -r osx-arm64 |
|
||||
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false |
arch="x64" |
||||
|
|
||||
|
if [[ $(uname -m) == 'arm64' ]]; then |
||||
|
arch="arm64" |
||||
|
fi |
||||
|
|
||||
|
dotnet restore -r osx-$arch |
||||
|
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-$arch -p:_AvaloniaUseExternalMSBuild=false |
||||
@ -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<SafeAreaChangedArgs> 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<WindowInsetsAnimationCompat> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Metadata; |
||||
|
|
||||
|
#nullable enable |
||||
|
namespace Avalonia.Controls.Platform |
||||
|
{ |
||||
|
[Unstable] |
||||
|
[NotClientImplementable] |
||||
|
public interface IInsetsManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the system bars are visible.
|
||||
|
/// </summary>
|
||||
|
bool? IsSystemBarVisible { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the window draws edge to edge. behind any visibile system bars.
|
||||
|
/// </summary>
|
||||
|
bool DisplayEdgeToEdge { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current safe area padding.
|
||||
|
/// </summary>
|
||||
|
Thickness SafeAreaPadding { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Occurs when safe area for the current window changes.
|
||||
|
/// </summary>
|
||||
|
event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged; |
||||
|
} |
||||
|
|
||||
|
public class SafeAreaChangedArgs : EventArgs |
||||
|
{ |
||||
|
public SafeAreaChangedArgs(Thickness safeArePadding) |
||||
|
{ |
||||
|
SafeAreaPadding = safeArePadding; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IInsetsManager.GetSafeAreaPadding"/>
|
||||
|
public Thickness SafeAreaPadding { get; } |
||||
|
} |
||||
|
|
||||
|
public enum SystemBarTheme |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Light system bar theme, with light background and a dark foreground
|
||||
|
/// </summary>
|
||||
|
Light, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Bark system bar theme, with dark background and a light foreground
|
||||
|
/// </summary>
|
||||
|
Dark |
||||
|
} |
||||
|
} |
||||
@ -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<SafeAreaChangedArgs>? 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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<SafeAreaChangedArgs>? SafeAreaChanged; |
||||
|
public event EventHandler<bool>? DisplayEdgeToEdgeChanged; |
||||
|
|
||||
|
public bool DisplayEdgeToEdge |
||||
|
{ |
||||
|
get => _displayEdgeToEdge; |
||||
|
set |
||||
|
{ |
||||
|
if (_displayEdgeToEdge != value) |
||||
|
{ |
||||
|
_displayEdgeToEdge = value; |
||||
|
DisplayEdgeToEdgeChanged?.Invoke(this, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Thickness SafeAreaPadding => _controller?.SafeAreaPadding ?? default; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IAvaloniaViewController" />
|
||||
|
public class DefaultAvaloniaViewController : UIViewController, IAvaloniaViewController |
||||
|
{ |
||||
|
private UIStatusBarStyle? _preferredStatusBarStyle; |
||||
|
private bool? _prefersStatusBarHidden; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool PrefersStatusBarHidden() |
||||
|
{ |
||||
|
return _prefersStatusBarHidden ??= base.PrefersStatusBarHidden(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Thickness SafeAreaPadding { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public event EventHandler SafeAreaPaddingChanged; |
||||
|
} |
||||
Loading…
Reference in new issue