Browse Source

Update insets API and implement insets for iOS with IAvaloniaViewController

pull/10145/head
Max Katz 4 years ago
parent
commit
87402e5b94
  1. 41
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  2. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  3. 43
      src/Avalonia.Controls/Platform/IInsetsManager.cs
  4. 14
      src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
  5. 4
      src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
  6. 35
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  7. 83
      src/iOS/Avalonia.iOS/InsetsManager.cs
  8. 74
      src/iOS/Avalonia.iOS/ViewController.cs

41
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -59,30 +59,39 @@ namespace Avalonia.Android.Platform
DisplayEdgeToEdge = false;
}
public Thickness GetSafeAreaPadding()
public Thickness SafeAreaPadding
{
var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
if (insets != null)
get
{
var renderScaling = _topLevel.RenderScaling;
var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
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());
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 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;
}
return default;
}
public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
{
NotifySafeAreaChanged(GetSafeAreaPadding());
NotifySafeAreaChanged(SafeAreaPadding);
return insets;
}
@ -93,7 +102,7 @@ namespace Avalonia.Android.Platform
public void OnGlobalLayout()
{
NotifySafeAreaChanged(GetSafeAreaPadding());
NotifySafeAreaChanged(SafeAreaPadding);
}
public SystemBarTheme? SystemBarTheme

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -43,7 +43,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager;
private readonly IInsetsManager _insetsManager;
private readonly AndroidInsetsManager _insetsManager;
private ViewImpl _view;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)

43
src/Avalonia.Controls/Platform/IInsetsManager.cs

@ -1,28 +1,18 @@
using System;
using Avalonia.Interactivity;
using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Controls.Platform
{
[Avalonia.Metadata.Unstable]
[Unstable]
[NotClientImplementable]
public interface IInsetsManager
{
/// <summary>
/// Gets or sets the theme for the system bars, if supported.
/// </summary>
SystemBarTheme? SystemBarTheme { get; set; }
/// <summary>
/// Gets or sets whether the system bars are visible.
/// </summary>
bool? IsSystemBarVisible { get; set; }
/// <summary>
/// Occurs when safe area for the current window changes.
/// </summary>
event EventHandler<SafeAreaChangedArgs> SafeAreaChanged;
/// <summary>
/// Gets or sets whether the window draws edge to edge. behind any visibile system bars.
/// </summary>
@ -31,18 +21,23 @@ namespace Avalonia.Controls.Platform
/// <summary>
/// Gets the current safe area padding.
/// </summary>
/// <returns></returns>
Thickness GetSafeAreaPadding();
public class SafeAreaChangedArgs : RoutedEventArgs
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)
{
public SafeAreaChangedArgs(Thickness safeArePadding)
{
SafeAreaPadding = safeArePadding;
}
public Thickness SafeAreaPadding { get; }
SafeAreaPadding = safeArePadding;
}
/// <inheritdoc cref="IInsetsManager.GetSafeAreaPadding"/>
public Thickness SafeAreaPadding { get; }
}
public enum SystemBarTheme

14
src/Browser/Avalonia.Browser/BrowserInsetsManager.cs

@ -11,7 +11,6 @@ namespace Avalonia.Browser
{
internal class BrowserInsetsManager : IInsetsManager
{
public SystemBarTheme? SystemBarTheme { get; set; }
public bool? IsSystemBarVisible
{
get
@ -20,7 +19,7 @@ namespace Avalonia.Browser
}
set
{
DomHelper.SetFullscreen(value != null ? !value.Value : false);
DomHelper.SetFullscreen(!value ?? false);
}
}
@ -28,16 +27,19 @@ namespace Avalonia.Browser
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
public Thickness GetSafeAreaPadding()
public Thickness SafeAreaPadding
{
var padding = DomHelper.GetSafeAreaPadding();
get
{
var padding = DomHelper.GetSafeAreaPadding();
return new Thickness(padding[0], padding[1], padding[2], padding[3]);
return new Thickness(padding[0], padding[1], padding[2], padding[3]);
}
}
public void NotifySafeAreaPaddingChanged()
{
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(GetSafeAreaPadding()));
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
}
}
}

4
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);

35
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;
}
/// <inheritdoc />
public override bool CanBecomeFirstResponder => true;
/// <inheritdoc />
public override bool CanResignFirstResponder => true;
/// <inheritdoc />
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
@ -60,6 +65,7 @@ namespace Avalonia.iOS
settings?.TraitCollectionDidChange();
}
/// <inheritdoc />
public override void TintColorDidChange()
{
base.TintColorDidChange();
@ -68,18 +74,31 @@ namespace Avalonia.iOS
settings?.TraitCollectionDidChange();
}
public void InitWithController<TController>(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;
}
}

83
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<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;
}

74
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;
}
/// <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…
Cancel
Save