diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/AppHostBuilderExtensions.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/AppHostBuilderExtensions.cs index 9fb132aba5b..cc28dcf9e6b 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/AppHostBuilderExtensions.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/AppHostBuilderExtensions.cs @@ -13,6 +13,7 @@ public static MauiAppBuilder UseSkiaSharp(this MauiAppBuilder builder) => .ConfigureMauiHandlers(handlers => { handlers.AddHandler(); + handlers.AddHandler(); }) .ConfigureImageSources(sources => { diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.HandlerImpl.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.HandlerImpl.cs deleted file mode 100644 index e1c4b27bde2..00000000000 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.HandlerImpl.cs +++ /dev/null @@ -1,38 +0,0 @@ -#nullable enable - -using System; - -namespace SkiaSharp.Views.Maui.Controls -{ - public partial class SKCanvasView : ISKCanvasView - { - private SKSizeI lastCanvasSize; - - public SKCanvasView() - { - var controller = (ISKCanvasViewController)this; - - controller.GetCanvasSize += OnGetCanvasSize; - controller.SurfaceInvalidated += OnSurfaceInvalidated; - - void OnGetCanvasSize(object? sender, GetPropertyValueEventArgs e) - { - e.Value = lastCanvasSize; - } - - void OnSurfaceInvalidated(object? sender, EventArgs e) - { - Handler?.Invoke(nameof(ISKCanvasView.InvalidateSurface)); - } - } - - void ISKCanvasView.OnCanvasSizeChanged(SKSizeI size) => - lastCanvasSize = size; - - void ISKCanvasView.OnPaintSurface(SKPaintSurfaceEventArgs e) => - OnPaintSurface(e); - - void ISKCanvasView.OnTouch(SKTouchEventArgs e) => - OnTouch(e); - } -} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.cs index e1b21e10823..9f4cb6172a5 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKCanvasView.cs @@ -8,7 +8,7 @@ namespace SkiaSharp.Views.Maui.Controls { - public partial class SKCanvasView : View, ISKCanvasViewController + public partial class SKCanvasView : View, ISKCanvasView { public static readonly BindableProperty IgnorePixelScalingProperty = BindableProperty.Create(nameof(IgnorePixelScaling), typeof(bool), typeof(SKCanvasView), false); @@ -16,99 +16,52 @@ public partial class SKCanvasView : View, ISKCanvasViewController public static readonly BindableProperty EnableTouchEventsProperty = BindableProperty.Create(nameof(EnableTouchEvents), typeof(bool), typeof(SKCanvasView), false); - // the user can subscribe to repaint + private SKSizeI lastCanvasSize; + + public SKCanvasView() + { + } + public event EventHandler? PaintSurface; - // the user can subscribe to touch events public event EventHandler? Touch; - // the native listens to this event - private event EventHandler? SurfaceInvalidated; - private event EventHandler>? GetCanvasSize; - - // the user asks the for the size - public SKSize CanvasSize - { - get - { - // send a mesage to the native view - var args = new GetPropertyValueEventArgs(); - GetCanvasSize?.Invoke(this, args); - return args.Value; - } - } + public SKSize CanvasSize => lastCanvasSize; public bool IgnorePixelScaling { - get { return (bool)GetValue(IgnorePixelScalingProperty); } - set { SetValue(IgnorePixelScalingProperty, value); } + get => (bool)GetValue(IgnorePixelScalingProperty); + set => SetValue(IgnorePixelScalingProperty, value); } public bool EnableTouchEvents { - get { return (bool)GetValue(EnableTouchEventsProperty); } - set { SetValue(EnableTouchEventsProperty, value); } + get => (bool)GetValue(EnableTouchEventsProperty); + set => SetValue(EnableTouchEventsProperty, value); } - // the user asks to repaint public void InvalidateSurface() { - // send a mesage to the native view - SurfaceInvalidated?.Invoke(this, EventArgs.Empty); + Handler?.Invoke(nameof(ISKCanvasView.InvalidateSurface)); } - // the native view tells the user to repaint protected virtual void OnPaintSurface(SKPaintSurfaceEventArgs e) { PaintSurface?.Invoke(this, e); } - // the native view responds to a touch protected virtual void OnTouch(SKTouchEventArgs e) { Touch?.Invoke(this, e); } - // ISKViewController implementation - - event EventHandler ISKCanvasViewController.SurfaceInvalidated - { - add { SurfaceInvalidated += value; } - remove { SurfaceInvalidated -= value; } - } - - event EventHandler> ISKCanvasViewController.GetCanvasSize - { - add { GetCanvasSize += value; } - remove { GetCanvasSize -= value; } - } + void ISKCanvasView.OnCanvasSizeChanged(SKSizeI size) => + lastCanvasSize = size; - void ISKCanvasViewController.OnPaintSurface(SKPaintSurfaceEventArgs e) - { + void ISKCanvasView.OnPaintSurface(SKPaintSurfaceEventArgs e) => OnPaintSurface(e); - } - void ISKCanvasViewController.OnTouch(SKTouchEventArgs e) - { + void ISKCanvasView.OnTouch(SKTouchEventArgs e) => OnTouch(e); - } - - protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) - { - return new SizeRequest(new Size(40.0, 40.0)); - } - } - - public interface ISKCanvasViewController : IViewController - { - // the native listens to this event - event EventHandler SurfaceInvalidated; - event EventHandler> GetCanvasSize; - - // the native view tells the user to repaint - void OnPaintSurface(SKPaintSurfaceEventArgs e); - - // the native view responds to a touch - void OnTouch(SKTouchEventArgs e); } } diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKGLView.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKGLView.cs index 519cbfc92d3..373f1a6ed2c 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKGLView.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKGLView.cs @@ -8,127 +8,71 @@ namespace SkiaSharp.Views.Maui.Controls { - public partial class SKGLView : View, ISKGLViewController + public partial class SKGLView : View, ISKGLView { + public static readonly BindableProperty IgnorePixelScalingProperty = + BindableProperty.Create(nameof(IgnorePixelScaling), typeof(bool), typeof(SKGLView), false); + public static readonly BindableProperty HasRenderLoopProperty = BindableProperty.Create(nameof(HasRenderLoop), typeof(bool), typeof(SKGLView), false); public static readonly BindableProperty EnableTouchEventsProperty = BindableProperty.Create(nameof(EnableTouchEvents), typeof(bool), typeof(SKGLView), false); + private SKSizeI lastCanvasSize; + private GRContext? lastGRContext; + + public bool IgnorePixelScaling + { + get => (bool)GetValue(IgnorePixelScalingProperty); + set => SetValue(IgnorePixelScalingProperty, value); + } + public bool HasRenderLoop { - get { return (bool)GetValue(HasRenderLoopProperty); } - set { SetValue(HasRenderLoopProperty, value); } + get => (bool)GetValue(HasRenderLoopProperty); + set => SetValue(HasRenderLoopProperty, value); } public bool EnableTouchEvents { - get { return (bool)GetValue(EnableTouchEventsProperty); } - set { SetValue(EnableTouchEventsProperty, value); } + get => (bool)GetValue(EnableTouchEventsProperty); + set => SetValue(EnableTouchEventsProperty, value); } - // the user can subscribe to repaint public event EventHandler? PaintSurface; - // the user can subscribe to touch events public event EventHandler? Touch; - // the native listens to this event - private event EventHandler? SurfaceInvalidated; - private event EventHandler>? GetCanvasSize; - private event EventHandler>? GetGRContext; + public SKSize CanvasSize => lastCanvasSize; - // the user asks the for the size - public SKSize CanvasSize - { - get - { - // send a mesage to the native view - var args = new GetPropertyValueEventArgs(); - GetCanvasSize?.Invoke(this, args); - return args.Value; - } - } + public GRContext? GRContext => lastGRContext; - // the user asks the for the current GRContext - public GRContext GRContext - { - get - { - // send a mesage to the native view - var args = new GetPropertyValueEventArgs(); - GetGRContext?.Invoke(this, args); - return args.Value; - } - } - - // the user asks to repaint public void InvalidateSurface() { - // send a mesage to the native view - SurfaceInvalidated?.Invoke(this, EventArgs.Empty); + Handler?.Invoke(nameof(ISKGLView.InvalidateSurface)); } - // the native view tells the user to repaint protected virtual void OnPaintSurface(SKPaintGLSurfaceEventArgs e) { PaintSurface?.Invoke(this, e); } - // the native view responds to a touch protected virtual void OnTouch(SKTouchEventArgs e) { Touch?.Invoke(this, e); } - // ISKViewController implementation - - event EventHandler ISKGLViewController.SurfaceInvalidated - { - add { SurfaceInvalidated += value; } - remove { SurfaceInvalidated -= value; } - } - - event EventHandler> ISKGLViewController.GetCanvasSize - { - add { GetCanvasSize += value; } - remove { GetCanvasSize -= value; } - } + void ISKGLView.OnCanvasSizeChanged(SKSizeI size) => + lastCanvasSize = size; - event EventHandler> ISKGLViewController.GetGRContext - { - add { GetGRContext += value; } - remove { GetGRContext -= value; } - } + void ISKGLView.OnGRContextChanged(GRContext? context) => + lastGRContext = context; - void ISKGLViewController.OnPaintSurface(SKPaintGLSurfaceEventArgs e) - { + void ISKGLView.OnPaintSurface(SKPaintGLSurfaceEventArgs e) => OnPaintSurface(e); - } - void ISKGLViewController.OnTouch(SKTouchEventArgs e) - { + void ISKGLView.OnTouch(SKTouchEventArgs e) => OnTouch(e); - } - - protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) - { - return new SizeRequest(new Size(40.0, 40.0)); - } - } - - public interface ISKGLViewController : IViewController - { - // the native listens to this event - event EventHandler SurfaceInvalidated; - event EventHandler> GetCanvasSize; - event EventHandler> GetGRContext; - - // the native view tells the user to repaint - void OnPaintSurface(SKPaintGLSurfaceEventArgs e); - - // the native view responds to a touch - void OnTouch(SKTouchEventArgs e); } } diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.HandlerImpl.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.HandlerImpl.cs deleted file mode 100644 index 695fa33f77d..00000000000 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.HandlerImpl.cs +++ /dev/null @@ -1,20 +0,0 @@ -#nullable enable - -namespace SkiaSharp.Views.Maui.Controls -{ - partial class SKImageImageSource : ISKImageImageSource - { - } - - partial class SKBitmapImageSource : ISKBitmapImageSource - { - } - - partial class SKPixmapImageSource : ISKPixmapImageSource - { - } - - partial class SKPictureImageSource : ISKPictureImageSource - { - } -} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.cs index 86bcb3e2fbc..aa0ad8b876d 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Controls/SKImageSource.cs @@ -5,7 +5,7 @@ namespace SkiaSharp.Views.Maui.Controls { - public sealed partial class SKImageImageSource : ImageSource + public sealed partial class SKImageImageSource : ImageSource, ISKImageImageSource { public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(SKImage), typeof(SKImageImageSource), default(SKImage)); @@ -41,7 +41,7 @@ protected override void OnPropertyChanged(string propertyName = null) } } - public sealed partial class SKBitmapImageSource : ImageSource + public sealed partial class SKBitmapImageSource : ImageSource, ISKBitmapImageSource { public static readonly BindableProperty BitmapProperty = BindableProperty.Create(nameof(Bitmap), typeof(SKBitmap), typeof(SKBitmapImageSource), default(SKBitmap)); @@ -77,7 +77,7 @@ protected override void OnPropertyChanged(string propertyName = null) } } - public sealed partial class SKPixmapImageSource : ImageSource + public sealed partial class SKPixmapImageSource : ImageSource, ISKPixmapImageSource { public static readonly BindableProperty PixmapProperty = BindableProperty.Create(nameof(Pixmap), typeof(SKPixmap), typeof(SKPixmapImageSource), default(SKPixmap)); @@ -113,7 +113,7 @@ protected override void OnPropertyChanged(string propertyName = null) } } - public sealed partial class SKPictureImageSource : ImageSource + public sealed partial class SKPictureImageSource : ImageSource, ISKPictureImageSource { public static readonly BindableProperty PictureProperty = BindableProperty.Create(nameof(Picture), typeof(SKPicture), typeof(SKPictureImageSource), default(SKPicture)); diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.iOS.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.Apple.cs similarity index 100% rename from source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.iOS.cs rename to source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.Apple.cs diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.Windows.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.Windows.cs index 9b6ba30935d..d25d6e3c58a 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.Windows.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.Windows.cs @@ -1,4 +1,5 @@ using Microsoft.Maui.Handlers; +using Microsoft.UI.Xaml; using SkiaSharp.Views.Maui.Platform; using SkiaSharp.Views.Windows; diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.cs index dd9deea98e7..90ea1bcd4f6 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKCanvasView/SKCanvasViewHandler.cs @@ -13,7 +13,7 @@ public partial class SKCanvasViewHandler }; public static CommandMapper SKCanvasViewCommandMapper = - new CommandMapper() + new CommandMapper(ViewHandler.ViewCommandMapper) { [nameof(ISKCanvasView.InvalidateSurface)] = OnInvalidateSurface, }; diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Android.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Android.cs new file mode 100644 index 00000000000..183f9fccf9a --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Android.cs @@ -0,0 +1,139 @@ +using Android.Content; +using Android.Opengl; +using Microsoft.Maui; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Platform; +using SkiaSharp.Views.Android; +using SkiaSharp.Views.Maui.Platform; + +namespace SkiaSharp.Views.Maui.Handlers +{ + public partial class SKGLViewHandler : ViewHandler + { + private SKSizeI lastCanvasSize; + private GRContext? lastGRContext; + private SKTouchHandler? touchHandler; + + protected override SKGLTextureView CreatePlatformView() + { + var view = new MauiSKGLTextureView(Context); + view.SetOpaque(false); + return view; + } + + protected override void ConnectHandler(SKGLTextureView platformView) + { + platformView.PaintSurface += OnPaintSurface; + + base.ConnectHandler(platformView); + } + + protected override void DisconnectHandler(SKGLTextureView platformView) + { + touchHandler?.Detach(platformView); + touchHandler = null; + + platformView.PaintSurface -= OnPaintSurface; + + base.DisconnectHandler(platformView); + } + + // Mapper actions / properties + + public static void OnInvalidateSurface(SKGLViewHandler handler, ISKGLView view, object? args) + { + if (handler.PlatformView.RenderMode == Rendermode.WhenDirty) + handler.PlatformView.RequestRender(); + } + + public static void MapIgnorePixelScaling(SKGLViewHandler handler, ISKGLView view) + { + if (handler.PlatformView is not MauiSKGLTextureView pv) + return; + + pv.IgnorePixelScaling = view.IgnorePixelScaling; + pv.RequestRender(); + } + + public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view) + { + handler.PlatformView.RenderMode = view.HasRenderLoop + ? Rendermode.Continuously + : Rendermode.WhenDirty; + } + + public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view) + { + if (handler.PlatformView == null) + return; + + handler.touchHandler ??= new SKTouchHandler( + args => view.OnTouch(args), + (x, y) => handler.OnGetScaledCoord(x, y)); + + handler.touchHandler?.SetEnabled(handler.PlatformView, view.EnableTouchEvents); + } + + // helper methods + + private void OnPaintSurface(object? sender, Android.SKPaintGLSurfaceEventArgs e) + { + var newCanvasSize = e.Info.Size; + if (lastCanvasSize != newCanvasSize) + { + lastCanvasSize = newCanvasSize; + VirtualView?.OnCanvasSizeChanged(newCanvasSize); + } + if (sender is SKGLTextureView platformView) + { + var newGRContext = platformView.GRContext; + if (lastGRContext != newGRContext) + { + lastGRContext = newGRContext; + VirtualView?.OnGRContextChanged(newGRContext); + } + } + + VirtualView?.OnPaintSurface(new SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info, e.RawInfo)); + } + + private SKPoint OnGetScaledCoord(double x, double y) + { + if (VirtualView?.IgnorePixelScaling == true && Context != null) + { + x = Context.FromPixels(x); + y = Context.FromPixels(y); + } + + return new SKPoint((float)x, (float)y); + } + + private class MauiSKGLTextureView : SKGLTextureView + { + private float density; + + public MauiSKGLTextureView(Context context) + : base(context) + { + density = Resources?.DisplayMetrics?.Density ?? 1; + } + + public bool IgnorePixelScaling { get; set; } + + protected override void OnPaintSurface(Android.SKPaintGLSurfaceEventArgs e) + { + if (IgnorePixelScaling) + { + var userVisibleSize = new SKSizeI((int)(e.Info.Width / density), (int)(e.Info.Height / density)); + var canvas = e.Surface.Canvas; + canvas.Scale(density); + canvas.Save(); + + e = new Android.SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info.WithSize(userVisibleSize), e.Info); + } + + base.OnPaintSurface(e); + } + } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.MacCatalyst.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.MacCatalyst.cs new file mode 100644 index 00000000000..81e91378e3c --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.MacCatalyst.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.Maui.Handlers; +using UIKit; + +namespace SkiaSharp.Views.Maui.Handlers +{ + public partial class SKGLViewHandler : ViewHandler + { + protected override UIView CreatePlatformView() => throw new PlatformNotSupportedException("OpenGL-based views (such as SKGLView) are not supported on Mac Catalyst. Instead, use Metal-based views."); + + public static void MapIgnorePixelScaling(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view) { } + + public static void OnInvalidateSurface(SKGLViewHandler handler, ISKGLView view, object? args) { } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Ref.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Ref.cs new file mode 100644 index 00000000000..c9ef2d859f4 --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Ref.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Maui.Handlers; + +namespace SkiaSharp.Views.Maui.Handlers +{ + public partial class SKGLViewHandler : ViewHandler + { + protected override object CreatePlatformView() => throw new NotImplementedException(); + + public static void MapIgnorePixelScaling(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view) { } + + public static void OnInvalidateSurface(SKGLViewHandler handler, ISKGLView view, object? args) { } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Tizen.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Tizen.cs new file mode 100644 index 00000000000..42a948c4534 --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Tizen.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Maui.Handlers; +using SkiaSharp.Views.Maui.Platform; +using SkiaSharp.Views.Tizen.NUI; +using ScalingInfo = SkiaSharp.Views.Tizen.ScalingInfo; + +namespace SkiaSharp.Views.Maui.Handlers +{ + public partial class SKGLViewHandler : ViewHandler + { + protected override SKGLSurfaceView CreatePlatformView() => throw new PlatformNotSupportedException("SKGLView is not yet implemented for Tizen."); + + public static void MapIgnorePixelScaling(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view) { } + + public static void OnInvalidateSurface(SKGLViewHandler handler, ISKGLView view, object? args) { } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Windows.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Windows.cs new file mode 100644 index 00000000000..d9b894d5be7 --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.Windows.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.Maui.Handlers; +using SkiaSharp.Views.Maui.Platform; +using SkiaSharp.Views.Windows; + +namespace SkiaSharp.Views.Maui.Handlers +{ + public partial class SKGLViewHandler : ViewHandler + { + private SKSizeI lastCanvasSize; + private SKTouchHandler? touchHandler; + + protected override FrameworkElement CreatePlatformView() => throw new PlatformNotSupportedException("SKGLView is not yet implemented for WinUI."); + + public static void MapIgnorePixelScaling(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view) { } + + public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view) { } + + public static void OnInvalidateSurface(SKGLViewHandler handler, ISKGLView view, object? args) { } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.cs new file mode 100644 index 00000000000..f25e6b07dbb --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.cs @@ -0,0 +1,32 @@ +using Microsoft.Maui; +using Microsoft.Maui.Handlers; + +namespace SkiaSharp.Views.Maui.Handlers +{ + public partial class SKGLViewHandler + { + public static PropertyMapper SKGLViewMapper = + new PropertyMapper(ViewHandler.ViewMapper) + { + [nameof(ISKGLView.EnableTouchEvents)] = MapEnableTouchEvents, + [nameof(ISKGLView.IgnorePixelScaling)] = MapIgnorePixelScaling, + [nameof(ISKGLView.HasRenderLoop)] = MapHasRenderLoop, + }; + + public static CommandMapper SKGLViewCommandMapper = + new CommandMapper(ViewHandler.ViewCommandMapper) + { + [nameof(ISKGLView.InvalidateSurface)] = OnInvalidateSurface, + }; + + public SKGLViewHandler() + : base(SKGLViewMapper, SKGLViewCommandMapper) + { + } + + public SKGLViewHandler(PropertyMapper? mapper, CommandMapper? commands) + : base(mapper ?? SKGLViewMapper, commands ?? SKGLViewCommandMapper) + { + } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.iOS.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.iOS.cs new file mode 100644 index 00000000000..83f3972865a --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKGLView/SKGLViewHandler.iOS.cs @@ -0,0 +1,222 @@ +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 + { + private SKSizeI lastCanvasSize; + private GRContext? lastGRContext; + private SKTouchHandler? touchHandler; + private RenderLoopManager? renderLoopManager; + + protected override SKGLView CreatePlatformView() => + new MauiSKGLView + { + BackgroundColor = UIColor.Clear, + Opaque = false, + }; + + protected override void ConnectHandler(SKGLView platformView) + { + renderLoopManager = new RenderLoopManager(this); + + platformView.PaintSurface += OnPaintSurface; + + base.ConnectHandler(platformView); + } + + protected override void DisconnectHandler(SKGLView platformView) + { + renderLoopManager?.StopRenderLoop(); + + touchHandler?.Detach(platformView); + touchHandler = null; + + platformView.PaintSurface -= OnPaintSurface; + + 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) + { + if (handler.PlatformView == null) + return; + + handler.touchHandler ??= new SKTouchHandler( + args => view.OnTouch(args), + (x, y) => handler.OnGetScaledCoord(x, y)); + + handler.touchHandler?.SetEnabled(handler.PlatformView, view.EnableTouchEvents); + } + + // helper methods + + private void OnPaintSurface(object? sender, iOS.SKPaintGLSurfaceEventArgs e) + { + var newCanvasSize = e.Info.Size; + if (lastCanvasSize != newCanvasSize) + { + lastCanvasSize = newCanvasSize; + VirtualView?.OnCanvasSizeChanged(newCanvasSize); + } + if (sender is SKGLView platformView) + { + var newGRContext = platformView.GRContext; + if (lastGRContext != newGRContext) + { + lastGRContext = newGRContext; + VirtualView?.OnGRContextChanged(newGRContext); + } + } + + VirtualView?.OnPaintSurface(new SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info, e.RawInfo)); + } + + private SKPoint OnGetScaledCoord(double x, double y) + { + if (VirtualView?.IgnorePixelScaling == false && PlatformView != null) + { + var scale = PlatformView.ContentScaleFactor; + + x *= scale; + y *= scale; + } + + return new SKPoint((float)x, (float)y); + } + + 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 weakHandler; + + public RenderLoopManager(SKGLViewHandler handler) + { + weakHandler = new WeakReference(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; + } + } + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKImageSourceService/SKImageSourceService.iOS.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKImageSourceService/SKImageSourceService.Apple.cs similarity index 100% rename from source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKImageSourceService/SKImageSourceService.iOS.cs rename to source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Handlers/SKImageSourceService/SKImageSourceService.Apple.cs diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/ISKGLView.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/ISKGLView.cs new file mode 100644 index 00000000000..55d517a5f0a --- /dev/null +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/ISKGLView.cs @@ -0,0 +1,27 @@ +using Microsoft.Maui; + +namespace SkiaSharp.Views.Maui +{ + public interface ISKGLView : IView + { + SKSize CanvasSize { get; } + + GRContext? GRContext { get; } + + bool HasRenderLoop { get; } + + bool IgnorePixelScaling { get; } + + bool EnableTouchEvents { get; } + + void InvalidateSurface(); + + void OnCanvasSizeChanged(SKSizeI size); + + void OnGRContextChanged(GRContext? context); + + void OnPaintSurface(SKPaintGLSurfaceEventArgs e); + + void OnTouch(SKTouchEventArgs e); + } +} diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/iOS/SKCanvasViewExtensions.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/Apple/SKCanvasViewExtensions.cs similarity index 100% rename from source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/iOS/SKCanvasViewExtensions.cs rename to source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/Apple/SKCanvasViewExtensions.cs diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/iOS/SKTouchHandler.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/Apple/SKTouchHandler.cs similarity index 100% rename from source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/iOS/SKTouchHandler.cs rename to source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/Platform/Apple/SKTouchHandler.cs diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SKPaintGLSurfaceEventArgs.cs b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SKPaintGLSurfaceEventArgs.cs index de5039c76ea..f1b7e3bc3d6 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SKPaintGLSurfaceEventArgs.cs +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SKPaintGLSurfaceEventArgs.cs @@ -18,6 +18,23 @@ public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget render BackendRenderTarget = renderTarget; ColorType = colorType; Origin = origin; + Info = new SKImageInfo(renderTarget.Width, renderTarget.Height, ColorType); + RawInfo = Info; + } + + public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget renderTarget, GRSurfaceOrigin origin, SKImageInfo info) + : this(surface, renderTarget, origin, info, info) + { + } + + public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget renderTarget, GRSurfaceOrigin origin, SKImageInfo info, SKImageInfo rawInfo) + { + Surface = surface; + BackendRenderTarget = renderTarget; + ColorType = info.ColorType; + Origin = origin; + Info = info; + RawInfo = rawInfo; } public SKSurface Surface { get; private set; } @@ -27,5 +44,9 @@ public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget render public SKColorType ColorType { get; private set; } public GRSurfaceOrigin Origin { get; private set; } + + public SKImageInfo Info { get; private set; } + + public SKImageInfo RawInfo { get; private set; } } } diff --git a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SkiaSharp.Views.Maui.Core.csproj b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SkiaSharp.Views.Maui.Core.csproj index c9c164a3919..ee09f8be269 100644 --- a/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SkiaSharp.Views.Maui.Core.csproj +++ b/source/SkiaSharp.Views.Maui/SkiaSharp.Views.Maui.Core/SkiaSharp.Views.Maui.Core.csproj @@ -27,11 +27,23 @@ + + + + + + + + + + + + diff --git a/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLLayer.cs b/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLLayer.cs index ee1a8eba55d..f27b363442b 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLLayer.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLLayer.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; +using System.Runtime.Versioning; using CoreAnimation; using CoreGraphics; using OpenGLES; @@ -13,6 +14,12 @@ namespace SkiaSharp.Views.tvOS namespace SkiaSharp.Views.iOS #endif { + [ObsoletedOSPlatform("tvos12.0", "Use 'Metal' instead.")] + [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")] + [SupportedOSPlatform("ios")] + [SupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("macos")] + [UnsupportedOSPlatform("maccatalyst")] public class SKGLLayer : CAEAGLLayer { private const SKColorType colorType = SKColorType.Rgba8888; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLView.cs b/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLView.cs index 58e10521ee8..de4b9d24557 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLView.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views/Platform/iOS/SKGLView.cs @@ -2,6 +2,8 @@ using System; using System.ComponentModel; +using System.Runtime.Versioning; +using CoreAnimation; using CoreGraphics; using Foundation; using GLKit; @@ -18,6 +20,11 @@ namespace SkiaSharp.Views.tvOS namespace SkiaSharp.Views.iOS #endif { + [ObsoletedOSPlatform("ios12.0", "Use 'Metal' instead.")] + [ObsoletedOSPlatform("tvos12.0", "Use 'Metal' instead.")] + [SupportedOSPlatform("ios")] + [SupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("macos")] [DesignTimeVisible(true)] #if HAS_UNO internal