From da16fecbc04b36b40e48e3b453aad372780246b7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 8 Jan 2024 21:42:18 -0800 Subject: [PATCH] Make iOS backend compilable with TVOS and MacCatalyst --- .../Compatibility/OperatingSystem.cs | 2 ++ src/iOS/Avalonia.iOS/AvaloniaView.cs | 34 +++++++++++++------ src/iOS/Avalonia.iOS/ClipboardImpl.cs | 2 ++ src/iOS/Avalonia.iOS/InsetsManager.cs | 7 ---- .../Storage/IOSSecurityScopedStream.cs | 4 ++- .../Avalonia.iOS/Storage/IOSStorageItem.cs | 4 ++- .../Storage/IOSStorageProvider.cs | 4 ++- src/iOS/Avalonia.iOS/TextInputResponder.cs | 5 ++- src/iOS/Avalonia.iOS/UIKitInputPane.cs | 13 ++++++- src/iOS/Avalonia.iOS/ViewController.cs | 8 +++++ 10 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Base/Compatibility/OperatingSystem.cs b/src/Avalonia.Base/Compatibility/OperatingSystem.cs index 838f7da8b2..eac199b32f 100644 --- a/src/Avalonia.Base/Compatibility/OperatingSystem.cs +++ b/src/Avalonia.Base/Compatibility/OperatingSystem.cs @@ -11,6 +11,7 @@ namespace Avalonia.Compatibility public static bool IsLinux() => OperatingSystem.IsLinux(); public static bool IsAndroid() => OperatingSystem.IsAndroid(); public static bool IsIOS() => OperatingSystem.IsIOS(); + public static bool IsTvOS() => OperatingSystem.IsTvOS(); public static bool IsBrowser() => OperatingSystem.IsBrowser(); public static bool IsOSPlatform(string platform) => OperatingSystem.IsOSPlatform(platform); #else @@ -19,6 +20,7 @@ namespace Avalonia.Compatibility public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); public static bool IsAndroid() => IsOSPlatform("ANDROID"); public static bool IsIOS() => IsOSPlatform("IOS"); + public static bool IsTvOS() => IsOSPlatform("TVOS"); // untested public static bool IsBrowser() => IsOSPlatform("BROWSER"); public static bool IsOSPlatform(string platform) => RuntimeInformation.IsOSPlatform(OSPlatform.Create(platform)); #endif diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index b9b8b78c1e..f44202c240 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -48,7 +48,9 @@ namespace Avalonia.iOS _topLevel.StartRendering(); InitLayerSurface(); +#if !TVOS MultipleTouchEnabled = true; +#endif } [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")] @@ -111,9 +113,10 @@ namespace Avalonia.iOS { private readonly AvaloniaView _view; private readonly INativeControlHostImpl _nativeControlHost; - private readonly IStorageProvider _storageProvider; internal readonly InsetsManager _insetsManager; - private readonly ClipboardImpl _clipboard; + private readonly IStorageProvider? _storageProvider; + private readonly IClipboard? _clipboard; + private readonly IInputPane? _inputPane; private IDisposable? _paddingInsets; public AvaloniaView View => _view; @@ -122,8 +125,12 @@ namespace Avalonia.iOS { _view = view; _nativeControlHost = new NativeControlHostImpl(view); +#if !TVOS _storageProvider = new IOSStorageProvider(view); - _insetsManager = new InsetsManager(view); + _clipboard = new ClipboardImpl(); + _inputPane = UIKitInputPane.Instance; +#endif + _insetsManager = new InsetsManager(); _insetsManager.DisplayEdgeToEdgeChanged += (_, edgeToEdge) => { // iOS doesn't add any paddings/margins to the application by itself. @@ -138,7 +145,6 @@ namespace Avalonia.iOS BindingPriority.Style); // lower priority, so it can be redefined by user } }; - _clipboard = new ClipboardImpl(); } public void Dispose() @@ -195,8 +201,11 @@ namespace Avalonia.iOS public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { +#if !TVOS // TODO adjust status bar depending on full screen mode. - if (OperatingSystem.IsIOSVersionAtLeast(13) && _view._controller is not null) + if ((OperatingSystem.IsIOSVersionAtLeast(13) + || OperatingSystem.IsMacCatalyst()) + && _view._controller is not null) { _view._controller.PreferredStatusBarStyle = themeVariant switch { @@ -205,6 +214,7 @@ namespace Avalonia.iOS _ => UIStatusBarStyle.Default }; } +#endif } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = @@ -212,11 +222,6 @@ namespace Avalonia.iOS public object? TryGetFeature(Type featureType) { - if (featureType == typeof(IStorageProvider)) - { - return _storageProvider; - } - if (featureType == typeof(ITextInputMethodImpl)) { return _view; @@ -232,15 +237,22 @@ namespace Avalonia.iOS return _insetsManager; } +#if !TVOS if (featureType == typeof(IClipboard)) { return _clipboard; } + if (featureType == typeof(IStorageProvider)) + { + return _storageProvider; + } + if (featureType == typeof(IInputPane)) { - return UIKitInputPane.Instance; + return _inputPane; } +#endif return null; } diff --git a/src/iOS/Avalonia.iOS/ClipboardImpl.cs b/src/iOS/Avalonia.iOS/ClipboardImpl.cs index 150f3424e3..0bc03e9160 100644 --- a/src/iOS/Avalonia.iOS/ClipboardImpl.cs +++ b/src/iOS/Avalonia.iOS/ClipboardImpl.cs @@ -1,3 +1,4 @@ +#if !TVOS using System; using System.Threading.Tasks; using Avalonia.Input; @@ -32,3 +33,4 @@ namespace Avalonia.iOS public Task GetDataAsync(string format) => Task.FromResult(null); } } +#endif diff --git a/src/iOS/Avalonia.iOS/InsetsManager.cs b/src/iOS/Avalonia.iOS/InsetsManager.cs index 54b769c567..c746644532 100644 --- a/src/iOS/Avalonia.iOS/InsetsManager.cs +++ b/src/iOS/Avalonia.iOS/InsetsManager.cs @@ -1,22 +1,15 @@ using System; using Avalonia.Controls.Platform; using Avalonia.Media; -using UIKit; namespace Avalonia.iOS; #nullable enable internal class InsetsManager : IInsetsManager { - private readonly AvaloniaView _view; private IAvaloniaViewController? _controller; private bool _displayEdgeToEdge = true; - public InsetsManager(AvaloniaView view) - { - _view = view; - } - internal void InitWithController(IAvaloniaViewController controller) { _controller = controller; diff --git a/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs b/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs index 424ec7589a..c78e74dc84 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs @@ -1,4 +1,5 @@ -using System.IO; +#if !TVOS +using System.IO; using Foundation; @@ -66,3 +67,4 @@ internal sealed class IOSSecurityScopedStream : Stream } } } +#endif diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index f6697777be..74fd9b7273 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -1,4 +1,5 @@ -using System; +#if !TVOS +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -275,3 +276,4 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder } } } +#endif diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs index 5c53cd2d2d..2848bfaba0 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs @@ -1,4 +1,5 @@ -using System; +#if !TVOS +using System; using System.Linq; using System.Collections.Generic; using System.Diagnostics; @@ -244,3 +245,4 @@ internal class IOSStorageProvider : IStorageProvider } } } +#endif diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs index a5a0c0759f..d3b518ae65 100644 --- a/src/iOS/Avalonia.iOS/TextInputResponder.cs +++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs @@ -108,7 +108,10 @@ partial class AvaloniaView { get { - var mode = UITextInputMode.CurrentInputMode; + UITextInputMode? mode = null; +#if !TVOS + mode = UITextInputMode.CurrentInputMode; +#endif // Can be empty see https://developer.apple.com/documentation/uikit/uitextinputmode/1614522-activeinputmodes if (mode is null && UITextInputMode.ActiveInputModes.Length > 0) { diff --git a/src/iOS/Avalonia.iOS/UIKitInputPane.cs b/src/iOS/Avalonia.iOS/UIKitInputPane.cs index 86cae0bf53..7ba6f8eb77 100644 --- a/src/iOS/Avalonia.iOS/UIKitInputPane.cs +++ b/src/iOS/Avalonia.iOS/UIKitInputPane.cs @@ -1,5 +1,7 @@ +#if !TVOS using System; using System.Diagnostics; +using System.Runtime.Versioning; using Avalonia.Animation.Easings; using Avalonia.Controls.Platform; using Foundation; @@ -8,6 +10,9 @@ using UIKit; #nullable enable namespace Avalonia.iOS; +[UnsupportedOSPlatform("tvos")] +[SupportedOSPlatform("maccatalyst")] +[SupportedOSPlatform("ios")] internal sealed class UIKitInputPane : IInputPane { public static UIKitInputPane Instance { get; } = new(); @@ -33,7 +38,11 @@ internal sealed class UIKitInputPane : IInputPane private void RaiseEventFromNotification(bool isUp, NSNotification notification) { State = isUp ? InputPaneState.Open : InputPaneState.Closed; - +#if MACCATALYST + OccludedRect = default; + StateChanged?.Invoke(this, new InputPaneStateEventArgs( + State, null, OccludedRect)); +#else var startFrame = UIKeyboard.FrameBeginFromNotification(notification); var endFrame = UIKeyboard.FrameEndFromNotification(notification); var duration = UIKeyboard.AnimationDurationFromNotification(notification); @@ -50,5 +59,7 @@ internal sealed class UIKitInputPane : IInputPane StateChanged?.Invoke(this, new InputPaneStateEventArgs( State, startRect, OccludedRect, TimeSpan.FromSeconds(duration), easing)); +#endif } } +#endif diff --git a/src/iOS/Avalonia.iOS/ViewController.cs b/src/iOS/Avalonia.iOS/ViewController.cs index 42a0949a9c..5f901fcf01 100644 --- a/src/iOS/Avalonia.iOS/ViewController.cs +++ b/src/iOS/Avalonia.iOS/ViewController.cs @@ -7,7 +7,9 @@ namespace Avalonia.iOS; [Unstable] public interface IAvaloniaViewController { +#if !TVOS UIStatusBarStyle PreferredStatusBarStyle { get; set; } +#endif bool PrefersStatusBarHidden { get; set; } Thickness SafeAreaPadding { get; } event EventHandler SafeAreaPaddingChanged; @@ -16,7 +18,9 @@ public interface IAvaloniaViewController /// public class DefaultAvaloniaViewController : UIViewController, IAvaloniaViewController { +#if !TVOS private UIStatusBarStyle? _preferredStatusBarStyle; +#endif private bool? _prefersStatusBarHidden; /// @@ -33,6 +37,7 @@ public class DefaultAvaloniaViewController : UIViewController, IAvaloniaViewCont } } +#if !TVOS /// public override bool PrefersStatusBarHidden() { @@ -55,6 +60,7 @@ public class DefaultAvaloniaViewController : UIViewController, IAvaloniaViewCont SetNeedsStatusBarAppearanceUpdate(); } } +#endif bool IAvaloniaViewController.PrefersStatusBarHidden { @@ -62,7 +68,9 @@ public class DefaultAvaloniaViewController : UIViewController, IAvaloniaViewCont set { _prefersStatusBarHidden = value; +#if !TVOS SetNeedsStatusBarAppearanceUpdate(); +#endif } }