1
0

AvaloniaView.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Runtime.Versioning;
  5. using Avalonia.Controls;
  6. using Avalonia.Controls.Embedding;
  7. using Avalonia.Controls.Platform;
  8. using Avalonia.Controls.Primitives;
  9. using Avalonia.Data;
  10. using Avalonia.Input;
  11. using Avalonia.Input.Platform;
  12. using Avalonia.Input.Raw;
  13. using Avalonia.Input.TextInput;
  14. using Avalonia.Platform;
  15. using Avalonia.Platform.Storage;
  16. using Avalonia.Rendering.Composition;
  17. using CoreAnimation;
  18. using Foundation;
  19. using ObjCRuntime;
  20. using UIKit;
  21. using IInsetsManager = Avalonia.Controls.Platform.IInsetsManager;
  22. namespace Avalonia.iOS
  23. {
  24. /// <summary>
  25. /// Root view container for Avalonia content, that can be embedded into iOS visual tree.
  26. /// </summary>
  27. public partial class AvaloniaView : UIView, ITextInputMethodImpl
  28. {
  29. internal IInputRoot InputRoot
  30. => _inputRoot ?? throw new InvalidOperationException($"{nameof(IWindowImpl.SetInputRoot)} must have been called");
  31. internal TopLevel TopLevel => _topLevel;
  32. private readonly TopLevelImpl _topLevelImpl;
  33. private readonly EmbeddableControlRoot _topLevel;
  34. private readonly InputHandler _input;
  35. private TextInputMethodClient? _client;
  36. private IAvaloniaViewController? _controller;
  37. private IInputRoot? _inputRoot;
  38. private Metal.MetalRenderTarget? _currentRenderTarget;
  39. private (PixelSize size, double scaling) _latestLayoutProps;
  40. public AvaloniaView()
  41. {
  42. _topLevelImpl = new TopLevelImpl(this);
  43. _input = new InputHandler(this, _topLevelImpl);
  44. _topLevel = new EmbeddableControlRoot(_topLevelImpl);
  45. _topLevel.Prepare();
  46. _topLevel.StartRendering();
  47. InitLayerSurface();
  48. // Remote touch handling
  49. if (OperatingSystem.IsTvOS())
  50. {
  51. AddGestureRecognizer(new UISwipeGestureRecognizer(_input.Handle)
  52. {
  53. Direction = UISwipeGestureRecognizerDirection.Up
  54. });
  55. AddGestureRecognizer(new UISwipeGestureRecognizer(_input.Handle)
  56. {
  57. Direction = UISwipeGestureRecognizerDirection.Right
  58. });
  59. AddGestureRecognizer(new UISwipeGestureRecognizer(_input.Handle)
  60. {
  61. Direction = UISwipeGestureRecognizerDirection.Down
  62. });
  63. AddGestureRecognizer(new UISwipeGestureRecognizer(_input.Handle)
  64. {
  65. Direction = UISwipeGestureRecognizerDirection.Left
  66. });
  67. }
  68. else if (OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst())
  69. {
  70. #if !TVOS
  71. MultipleTouchEnabled = true;
  72. #endif
  73. }
  74. }
  75. [SuppressMessage("Interoperability", "CA1422:Validate platform compatibility")]
  76. private void InitLayerSurface()
  77. {
  78. var l = Layer;
  79. l.ContentsScale = UIScreen.MainScreen.Scale;
  80. l.Opaque = true;
  81. #if !MACCATALYST
  82. if (l is CAEAGLLayer eaglLayer)
  83. {
  84. eaglLayer.DrawableProperties = new NSDictionary(
  85. OpenGLES.EAGLDrawableProperty.RetainedBacking, false,
  86. OpenGLES.EAGLDrawableProperty.ColorFormat, OpenGLES.EAGLColorFormat.RGBA8
  87. );
  88. _topLevelImpl.Surfaces = new[] { new Eagl.EaglLayerSurface(eaglLayer) };
  89. }
  90. else
  91. #endif
  92. if (l is CAMetalLayer metalLayer)
  93. {
  94. metalLayer.Opaque = false;
  95. _topLevelImpl.Surfaces = new[] { new Metal.MetalPlatformSurface(metalLayer, this) };
  96. }
  97. }
  98. /// <inheritdoc />
  99. public override bool CanBecomeFirstResponder => true;
  100. /// <inheritdoc />
  101. public override bool CanResignFirstResponder => true;
  102. /// <inheritdoc />
  103. [ObsoletedOSPlatform("ios17.0", "Use the 'UITraitChangeObservable' protocol instead.")]
  104. [ObsoletedOSPlatform("maccatalyst17.0", "Use the 'UITraitChangeObservable' protocol instead.")]
  105. [ObsoletedOSPlatform("tvos17.0", "Use the 'UITraitChangeObservable' protocol instead.")]
  106. [SupportedOSPlatform("ios")]
  107. [SupportedOSPlatform("tvos")]
  108. [SupportedOSPlatform("maccatalyst")]
  109. public override void TraitCollectionDidChange(UITraitCollection? previousTraitCollection)
  110. {
  111. base.TraitCollectionDidChange(previousTraitCollection);
  112. var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as PlatformSettings;
  113. settings?.TraitCollectionDidChange();
  114. }
  115. /// <inheritdoc />
  116. public override void TintColorDidChange()
  117. {
  118. base.TintColorDidChange();
  119. var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as PlatformSettings;
  120. settings?.TraitCollectionDidChange();
  121. }
  122. public void InitWithController<TController>(TController controller)
  123. where TController : UIViewController, IAvaloniaViewController
  124. {
  125. _controller = controller;
  126. _topLevelImpl._insetsManager.InitWithController(controller);
  127. }
  128. internal class TopLevelImpl : ITopLevelImpl
  129. {
  130. private readonly AvaloniaView _view;
  131. private readonly INativeControlHostImpl _nativeControlHost;
  132. internal readonly InsetsManager _insetsManager;
  133. private readonly IStorageProvider? _storageProvider;
  134. private readonly IClipboard? _clipboard;
  135. private readonly IInputPane? _inputPane;
  136. private IDisposable? _paddingInsets;
  137. public AvaloniaView View => _view;
  138. public TopLevelImpl(AvaloniaView view)
  139. {
  140. _view = view;
  141. _nativeControlHost = new NativeControlHostImpl(view);
  142. #if TVOS
  143. _storageProvider = null;
  144. _clipboard = null;
  145. _inputPane = null;
  146. #else
  147. _storageProvider = new Storage.IOSStorageProvider(view);
  148. _clipboard = new ClipboardImpl();
  149. _inputPane = UIKitInputPane.Instance;
  150. #endif
  151. _insetsManager = new InsetsManager();
  152. _insetsManager.DisplayEdgeToEdgeChanged += (_, edgeToEdge) =>
  153. {
  154. // iOS doesn't add any paddings/margins to the application by itself.
  155. // Application is fully responsible for safe area paddings.
  156. // So, unlikely to android, we need to "fake" safe area insets when edge to edge is disabled.
  157. _paddingInsets?.Dispose();
  158. if (!edgeToEdge && view._controller is { } controller)
  159. {
  160. _paddingInsets = view._topLevel.SetValue(
  161. TemplatedControl.PaddingProperty,
  162. controller.SafeAreaPadding,
  163. BindingPriority.Style); // lower priority, so it can be redefined by user
  164. }
  165. };
  166. }
  167. public void Dispose()
  168. {
  169. // No-op
  170. }
  171. public Compositor Compositor => Platform.Compositor
  172. ?? throw new InvalidOperationException("iOS backend wasn't initialized. Make sure UseiOS was called.");
  173. public void Invalidate(Rect rect)
  174. {
  175. // No-op
  176. }
  177. public void SetInputRoot(IInputRoot inputRoot)
  178. {
  179. _view._inputRoot = inputRoot;
  180. }
  181. public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
  182. public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y);
  183. public void SetCursor(ICursorImpl? cursor)
  184. {
  185. // no-op
  186. }
  187. public IPopupImpl? CreatePopup()
  188. {
  189. // In-window popups
  190. return null;
  191. }
  192. public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel)
  193. {
  194. // No-op
  195. }
  196. public double DesktopScaling => RenderScaling;
  197. public IPlatformHandle? Handle { get; }
  198. public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height);
  199. public Size? FrameSize => null;
  200. public double RenderScaling => _view.ContentScaleFactor;
  201. public IEnumerable<object> Surfaces { get; set; } = Array.Empty<object>();
  202. public Action<RawInputEventArgs>? Input { get; set; }
  203. public Action<Rect>? Paint { get; set; }
  204. public Action<Size, WindowResizeReason>? Resized { get; set; }
  205. public Action<double>? ScalingChanged { get; set; }
  206. public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
  207. public Action? Closed { get; set; }
  208. public Action? LostFocus { get; set; }
  209. public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
  210. public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
  211. {
  212. #if !TVOS
  213. // TODO adjust status bar depending on full screen mode.
  214. if ((OperatingSystem.IsIOSVersionAtLeast(13)
  215. || OperatingSystem.IsMacCatalyst())
  216. && _view._controller is not null)
  217. {
  218. _view._controller.PreferredStatusBarStyle = themeVariant switch
  219. {
  220. PlatformThemeVariant.Light => UIStatusBarStyle.DarkContent,
  221. PlatformThemeVariant.Dark => UIStatusBarStyle.LightContent,
  222. _ => UIStatusBarStyle.Default
  223. };
  224. }
  225. #endif
  226. }
  227. public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
  228. new AcrylicPlatformCompensationLevels();
  229. public object? TryGetFeature(Type featureType)
  230. {
  231. if (featureType == typeof(ITextInputMethodImpl))
  232. {
  233. return _view;
  234. }
  235. if (featureType == typeof(INativeControlHostImpl))
  236. {
  237. return _nativeControlHost;
  238. }
  239. if (featureType == typeof(IInsetsManager))
  240. {
  241. return _insetsManager;
  242. }
  243. if (featureType == typeof(IClipboard))
  244. {
  245. return _clipboard;
  246. }
  247. if (featureType == typeof(IStorageProvider))
  248. {
  249. return _storageProvider;
  250. }
  251. if (featureType == typeof(IInputPane))
  252. {
  253. return _inputPane;
  254. }
  255. if (featureType == typeof(ILauncher))
  256. {
  257. return new IOSLauncher();
  258. }
  259. return null;
  260. }
  261. }
  262. [Export("layerClass")]
  263. public static Class LayerClass()
  264. {
  265. #if !MACCATALYST
  266. if (Platform.Graphics is Eagl.EaglPlatformGraphics)
  267. {
  268. return new Class(typeof(CAEAGLLayer));
  269. }
  270. else
  271. #endif
  272. {
  273. return new Class(typeof(CAMetalLayer));
  274. }
  275. }
  276. /// <inheritdoc/>
  277. public override void TouchesBegan(NSSet touches, UIEvent? evt) => _input.Handle(touches, evt);
  278. /// <inheritdoc/>
  279. public override void TouchesMoved(NSSet touches, UIEvent? evt) => _input.Handle(touches, evt);
  280. /// <inheritdoc/>
  281. public override void TouchesEnded(NSSet touches, UIEvent? evt) => _input.Handle(touches, evt);
  282. /// <inheritdoc/>
  283. public override void TouchesCancelled(NSSet touches, UIEvent? evt) => _input.Handle(touches, evt);
  284. /// <inheritdoc/>
  285. public override void PressesBegan(NSSet<UIPress> presses, UIPressesEvent evt)
  286. {
  287. if (!_input.Handle(presses, evt))
  288. {
  289. base.PressesBegan(presses, evt);
  290. }
  291. }
  292. /// <inheritdoc/>
  293. public override void PressesChanged(NSSet<UIPress> presses, UIPressesEvent evt)
  294. {
  295. if (!_input.Handle(presses, evt))
  296. {
  297. base.PressesBegan(presses, evt);
  298. }
  299. }
  300. /// <inheritdoc/>
  301. public override void PressesEnded(NSSet<UIPress> presses, UIPressesEvent evt)
  302. {
  303. if (!_input.Handle(presses, evt))
  304. {
  305. base.PressesEnded(presses, evt);
  306. }
  307. }
  308. /// <inheritdoc/>
  309. public override void PressesCancelled(NSSet<UIPress> presses, UIPressesEvent evt)
  310. {
  311. if (!_input.Handle(presses, evt))
  312. {
  313. base.PressesCancelled(presses, evt);
  314. }
  315. }
  316. /// <inheritdoc/>
  317. public override void LayoutSubviews()
  318. {
  319. _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize, WindowResizeReason.Layout);
  320. var scaling = (double)ContentScaleFactor;
  321. _latestLayoutProps = (new PixelSize((int)(Bounds.Width * scaling), (int)(Bounds.Height * scaling)), scaling);
  322. if (_currentRenderTarget is not null)
  323. {
  324. _currentRenderTarget.PendingLayout = _latestLayoutProps;
  325. }
  326. base.LayoutSubviews();
  327. }
  328. public Control? Content
  329. {
  330. get => (Control?)_topLevel.Content;
  331. set => _topLevel.Content = value;
  332. }
  333. internal void SetRenderTarget(Metal.MetalRenderTarget target)
  334. {
  335. _currentRenderTarget = target;
  336. _currentRenderTarget.PendingLayout = _latestLayoutProps;
  337. }
  338. }
  339. }