11 changed files with 382 additions and 20 deletions
@ -0,0 +1,215 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Android.OS; |
|||
using Android.Views; |
|||
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); |
|||
} |
|||
} |
|||
|
|||
public Thickness GetSafeAreaPadding() |
|||
{ |
|||
var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView); |
|||
|
|||
if (insets != null) |
|||
{ |
|||
var renderScaling = _topLevel.RenderScaling; |
|||
|
|||
var inset = insets.GetInsets((DisplayEdgeToEdge ? WindowInsetsCompat.Type.SystemBars() | 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(GetSafeAreaPadding()); |
|||
return insets; |
|||
} |
|||
|
|||
private void NotifySafeAreaChanged(Thickness safeAreaPadding) |
|||
{ |
|||
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding)); |
|||
} |
|||
|
|||
public void OnGlobalLayout() |
|||
{ |
|||
NotifySafeAreaChanged(GetSafeAreaPadding()); |
|||
} |
|||
|
|||
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; |
|||
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
public bool? IsSystemBarVisible |
|||
{ |
|||
get |
|||
{ |
|||
var compat = ViewCompat.GetRootWindowInsets(_topLevel.View); |
|||
|
|||
return compat?.IsVisible(WindowInsetsCompat.Type.SystemBars()); |
|||
} |
|||
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.SystemBars()); |
|||
} |
|||
else |
|||
{ |
|||
compat?.Hide(WindowInsetsCompat.Type.SystemBars()); |
|||
|
|||
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.SystemBars() | 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,34 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
[Avalonia.Metadata.Unstable] |
|||
public interface IInsetsManager |
|||
{ |
|||
SystemBarTheme? SystemBarTheme { get; set; } |
|||
|
|||
bool? IsSystemBarVisible { get; set; } |
|||
|
|||
event EventHandler<SafeAreaChangedArgs> SafeAreaChanged; |
|||
|
|||
bool DisplayEdgeToEdge { get; set; } |
|||
|
|||
Thickness GetSafeAreaPadding(); |
|||
|
|||
public class SafeAreaChangedArgs : EventArgs |
|||
{ |
|||
public SafeAreaChangedArgs(Thickness safeArePadding) |
|||
{ |
|||
SafeAreaPadding = safeArePadding; |
|||
} |
|||
|
|||
public Thickness SafeAreaPadding { get; } |
|||
} |
|||
} |
|||
|
|||
public enum SystemBarTheme |
|||
{ |
|||
Light, |
|||
Dark |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
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 SystemBarTheme? SystemBarTheme { get; set; } |
|||
public bool? IsSystemBarVisible |
|||
{ |
|||
get |
|||
{ |
|||
return DomHelper.IsFullscreen(); |
|||
} |
|||
set |
|||
{ |
|||
DomHelper.SetFullscreen(value != null ? !value.Value : false); |
|||
} |
|||
} |
|||
|
|||
public bool DisplayEdgeToEdge { get; set; } |
|||
|
|||
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged; |
|||
|
|||
public Thickness GetSafeAreaPadding() |
|||
{ |
|||
var padding = DomHelper.GetSafeAreaPadding(); |
|||
|
|||
return new Thickness(padding[0], padding[1], padding[2], padding[3]); |
|||
} |
|||
|
|||
public void NotifySafeAreaPaddingChanged() |
|||
{ |
|||
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(GetSafeAreaPadding())); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue