source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.iOS.cs (173 lines of code) (raw):

using System; using System.Runtime.Versioning; using CoreAnimation; using Foundation; using Microsoft.Maui.Handlers; using SkiaSharp.Views.iOS; using SkiaSharp.Views.Maui.Platform; using UIKit; namespace SkiaSharp.Views.Maui.Handlers { [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")] [ObsoletedOSPlatform("tvos12.0", "Use 'Metal' instead.")] [SupportedOSPlatform("ios")] [SupportedOSPlatform("tvos")] [UnsupportedOSPlatform("macos")] public partial class SKGLViewHandler : ViewHandler<ISKGLView, SKGLView> { private PaintSurfaceProxy? paintSurfaceProxy; private SKTouchHandlerProxy? touchProxy; private RenderLoopManager? renderLoopManager; protected override SKGLView CreatePlatformView() => new MauiSKGLView { BackgroundColor = UIColor.Clear, Opaque = false, }; protected override void ConnectHandler(SKGLView platformView) { paintSurfaceProxy = new(); paintSurfaceProxy.Connect(VirtualView, platformView); touchProxy = new(); touchProxy.Connect(VirtualView, platformView); renderLoopManager = new RenderLoopManager(this); base.ConnectHandler(platformView); } protected override void DisconnectHandler(SKGLView platformView) { paintSurfaceProxy?.Disconnect(platformView); paintSurfaceProxy = null; touchProxy?.Disconnect(platformView); touchProxy = null; renderLoopManager?.StopRenderLoop(); base.DisconnectHandler(platformView); } // Mapper actions / properties public static void OnInvalidateSurface(SKGLViewHandler handler, ISKGLView view, object? args) { handler.renderLoopManager?.RequestDisplay(); } public static void MapIgnorePixelScaling(SKGLViewHandler handler, ISKGLView view) { if (handler.PlatformView is MauiSKGLView pv) { pv.IgnorePixelScaling = view.IgnorePixelScaling; handler.renderLoopManager?.RequestDisplay(); } } public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view) { if (view.HasRenderLoop) handler.renderLoopManager?.RequestRenderLoop(); else handler.renderLoopManager?.StopRenderLoop(); } public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view) { handler.touchProxy?.UpdateEnableTouchEvents(handler.PlatformView, view.EnableTouchEvents); } // helper methods private class MauiSKGLView : SKGLView { public bool IgnorePixelScaling { get; set; } protected override void OnPaintSurface(iOS.SKPaintGLSurfaceEventArgs e) { if (IgnorePixelScaling) { var userVisibleSize = new SKSizeI((int)Bounds.Width, (int)Bounds.Height); var canvas = e.Surface.Canvas; canvas.Scale((float)ContentScaleFactor); canvas.Save(); e = new iOS.SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info.WithSize(userVisibleSize), e.Info); } base.OnPaintSurface(e); } } private class RenderLoopManager { private CADisplayLink? displayLink; private WeakReference<SKGLViewHandler> weakHandler; public RenderLoopManager(SKGLViewHandler handler) { weakHandler = new WeakReference<SKGLViewHandler>(handler); } public SKGLViewHandler? Handler { get { if (weakHandler.TryGetTarget(out var handler)) return handler; return null; } } public SKGLView? PlatformView => Handler?.PlatformView; public ISKGLView? VirtualView => Handler?.VirtualView; public void RequestDisplay() { // skip if there is a render loop if (displayLink is not null) return; var nativeView = PlatformView; nativeView?.BeginInvokeOnMainThread(() => { if (nativeView is not null && nativeView.Handle != IntPtr.Zero) nativeView.Display(); }); } public void RequestRenderLoop() { // skip if there is already a render loop if (displayLink is not null) return; // bail out if we are requesting something that the view doesn't want to if (VirtualView?.HasRenderLoop != true) return; displayLink = CADisplayLink.Create(() => { var nativeView = PlatformView; var virtualView = VirtualView; // stop the render loop if the loop was disabled, or the views are disposed if (nativeView is null || virtualView is null || nativeView.Handle == IntPtr.Zero || !virtualView.HasRenderLoop) { StopRenderLoop(); return; } // redraw the view nativeView.Display(); }); displayLink.AddToRunLoop(NSRunLoop.Current, NSRunLoopMode.Default); } public void StopRenderLoop() { // skip if there is no render loop if (displayLink is null) return; displayLink.Invalidate(); displayLink.Dispose(); displayLink = null; } } private class PaintSurfaceProxy : SKEventProxy<ISKGLView, SKGLView> { private SKSizeI lastCanvasSize; private GRContext? lastGRContext; protected override void OnConnect(ISKGLView virtualView, SKGLView platformView) => platformView.PaintSurface += OnPaintSurface; protected override void OnDisconnect(SKGLView platformView) => platformView.PaintSurface -= OnPaintSurface; private void OnPaintSurface(object? sender, iOS.SKPaintGLSurfaceEventArgs e) { if (VirtualView is not {} view) return; var newCanvasSize = e.Info.Size; if (lastCanvasSize != newCanvasSize) { lastCanvasSize = newCanvasSize; view.OnCanvasSizeChanged(newCanvasSize); } if (sender is SKGLView platformView) { var newGRContext = platformView.GRContext; if (lastGRContext != newGRContext) { lastGRContext = newGRContext; view.OnGRContextChanged(newGRContext); } } view.OnPaintSurface(new SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info, e.RawInfo)); } } } }