зеркало из https://github.com/mono/SkiaSharp.git
Родитель
d3a6ae9491
Коммит
ccde024b62
|
@ -1,7 +1,7 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
|
@ -10,6 +10,9 @@ namespace SkiaSharp.Views.Maui.Controls
|
|||
{
|
||||
public partial class SKGLView : View, ISKGLView
|
||||
{
|
||||
private static readonly BindableProperty ProxyWindowProperty =
|
||||
BindableProperty.Create("ProxyWindow", typeof(Window), typeof(SKGLView), propertyChanged: OnWindowChanged);
|
||||
|
||||
public static readonly BindableProperty IgnorePixelScalingProperty =
|
||||
BindableProperty.Create(nameof(IgnorePixelScaling), typeof(bool), typeof(SKGLView), false);
|
||||
|
||||
|
@ -22,6 +25,12 @@ namespace SkiaSharp.Views.Maui.Controls
|
|||
private SKSizeI lastCanvasSize;
|
||||
private GRContext? lastGRContext;
|
||||
|
||||
public SKGLView()
|
||||
{
|
||||
var binding = new Binding(nameof(Window), source: this);
|
||||
SetBinding(ProxyWindowProperty, binding);
|
||||
}
|
||||
|
||||
public bool IgnorePixelScaling
|
||||
{
|
||||
get => (bool)GetValue(IgnorePixelScalingProperty);
|
||||
|
@ -63,6 +72,17 @@ namespace SkiaSharp.Views.Maui.Controls
|
|||
Touch?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private static void OnWindowChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is not SKGLView view)
|
||||
return;
|
||||
|
||||
view.Handler?.UpdateValue(nameof(HasRenderLoop));
|
||||
}
|
||||
|
||||
bool ISKGLView.HasRenderLoop =>
|
||||
HasRenderLoop && Window is not null;
|
||||
|
||||
void ISKGLView.OnCanvasSizeChanged(SKSizeI size) =>
|
||||
lastCanvasSize = size;
|
||||
|
||||
|
|
|
@ -7,24 +7,27 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
{
|
||||
public partial class SKCanvasViewHandler : ViewHandler<ISKCanvasView, SKCanvasView>
|
||||
{
|
||||
private SKSizeI lastCanvasSize;
|
||||
private SKTouchHandler? touchHandler;
|
||||
private PaintSurfaceProxy? paintSurfaceProxy;
|
||||
private SKTouchHandlerProxy? touchProxy;
|
||||
|
||||
protected override SKCanvasView CreatePlatformView() => new SKCanvasView { BackgroundColor = UIColor.Clear };
|
||||
|
||||
protected override void ConnectHandler(SKCanvasView platformView)
|
||||
{
|
||||
platformView.PaintSurface += OnPaintSurface;
|
||||
paintSurfaceProxy = new();
|
||||
paintSurfaceProxy.Connect(VirtualView, platformView);
|
||||
touchProxy = new();
|
||||
touchProxy.Connect(VirtualView, platformView);
|
||||
|
||||
base.ConnectHandler(platformView);
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SKCanvasView platformView)
|
||||
{
|
||||
touchHandler?.Detach(platformView);
|
||||
touchHandler = null;
|
||||
|
||||
platformView.PaintSurface -= OnPaintSurface;
|
||||
paintSurfaceProxy?.Disconnect(platformView);
|
||||
paintSurfaceProxy = null;
|
||||
touchProxy?.Disconnect(platformView);
|
||||
touchProxy = null;
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
@ -43,38 +46,35 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
|
||||
public static void MapEnableTouchEvents(SKCanvasViewHandler handler, ISKCanvasView canvasView)
|
||||
{
|
||||
handler.touchHandler ??= new SKTouchHandler(
|
||||
args => canvasView.OnTouch(args),
|
||||
(x, y) => handler.OnGetScaledCoord(x, y));
|
||||
|
||||
handler.touchHandler?.SetEnabled(handler.PlatformView, canvasView.EnableTouchEvents);
|
||||
handler.touchProxy?.UpdateEnableTouchEvents(handler.PlatformView, canvasView.EnableTouchEvents);
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
private class PaintSurfaceProxy : SKEventProxy<ISKCanvasView, SKCanvasView>
|
||||
{
|
||||
private SKSizeI lastCanvasSize;
|
||||
|
||||
protected override void OnConnect(ISKCanvasView virtualView, SKCanvasView platformView) =>
|
||||
platformView.PaintSurface += OnPaintSurface;
|
||||
|
||||
protected override void OnDisconnect(SKCanvasView platformView) =>
|
||||
platformView.PaintSurface -= OnPaintSurface;
|
||||
|
||||
private void OnPaintSurface(object? sender, iOS.SKPaintSurfaceEventArgs e)
|
||||
{
|
||||
if (VirtualView is not {} view)
|
||||
return;
|
||||
|
||||
var newCanvasSize = e.Info.Size;
|
||||
if (lastCanvasSize != newCanvasSize)
|
||||
{
|
||||
lastCanvasSize = newCanvasSize;
|
||||
VirtualView?.OnCanvasSizeChanged(newCanvasSize);
|
||||
view.OnCanvasSizeChanged(newCanvasSize);
|
||||
}
|
||||
|
||||
VirtualView?.OnPaintSurface(new SKPaintSurfaceEventArgs(e.Surface, e.Info, e.RawInfo));
|
||||
view.OnPaintSurface(new SKPaintSurfaceEventArgs(e.Surface, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,8 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
{
|
||||
public partial class SKGLViewHandler : ViewHandler<ISKGLView, SKMetalView>
|
||||
{
|
||||
private SKSizeI lastCanvasSize;
|
||||
private GRContext? lastGRContext;
|
||||
private SKTouchHandler? touchHandler;
|
||||
private PaintSurfaceProxy? paintSurfaceProxy;
|
||||
private SKTouchHandlerProxy? touchProxy;
|
||||
|
||||
protected override SKMetalView CreatePlatformView() =>
|
||||
new MauiSKMetalView
|
||||
|
@ -21,17 +20,20 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
|
||||
protected override void ConnectHandler(SKMetalView platformView)
|
||||
{
|
||||
platformView.PaintSurface += OnPaintSurface;
|
||||
paintSurfaceProxy = new();
|
||||
paintSurfaceProxy.Connect(VirtualView, platformView);
|
||||
touchProxy = new();
|
||||
touchProxy.Connect(VirtualView, platformView);
|
||||
|
||||
base.ConnectHandler(platformView);
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SKMetalView platformView)
|
||||
{
|
||||
touchHandler?.Detach(platformView);
|
||||
touchHandler = null;
|
||||
|
||||
platformView.PaintSurface -= OnPaintSurface;
|
||||
paintSurfaceProxy?.Disconnect(platformView);
|
||||
paintSurfaceProxy = null;
|
||||
touchProxy?.Disconnect(platformView);
|
||||
touchProxy = null;
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
@ -61,49 +63,11 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
|
||||
public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view)
|
||||
{
|
||||
handler.touchHandler ??= new SKTouchHandler(
|
||||
args => view.OnTouch(args),
|
||||
(x, y) => handler.OnGetScaledCoord(x, y));
|
||||
|
||||
handler.touchHandler?.SetEnabled(handler.PlatformView, view.EnableTouchEvents);
|
||||
handler.touchProxy?.UpdateEnableTouchEvents(handler.PlatformView, view.EnableTouchEvents);
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
private void OnPaintSurface(object? sender, iOS.SKPaintMetalSurfaceEventArgs e)
|
||||
{
|
||||
var newCanvasSize = e.Info.Size;
|
||||
if (lastCanvasSize != newCanvasSize)
|
||||
{
|
||||
lastCanvasSize = newCanvasSize;
|
||||
VirtualView?.OnCanvasSizeChanged(newCanvasSize);
|
||||
}
|
||||
if (sender is SKMetalView 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 MauiSKMetalView : SKMetalView
|
||||
{
|
||||
public bool IgnorePixelScaling { get; set; }
|
||||
|
@ -123,5 +87,41 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
base.OnPaintSurface(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class PaintSurfaceProxy : SKEventProxy<ISKGLView, SKMetalView>
|
||||
{
|
||||
private SKSizeI lastCanvasSize;
|
||||
private GRContext? lastGRContext;
|
||||
|
||||
protected override void OnConnect(ISKGLView virtualView, SKMetalView platformView) =>
|
||||
platformView.PaintSurface += OnPaintSurface;
|
||||
|
||||
protected override void OnDisconnect(SKMetalView platformView) =>
|
||||
platformView.PaintSurface -= OnPaintSurface;
|
||||
|
||||
private void OnPaintSurface(object? sender, iOS.SKPaintMetalSurfaceEventArgs e)
|
||||
{
|
||||
if (VirtualView is not {} view)
|
||||
return;
|
||||
|
||||
var newCanvasSize = e.Info.Size;
|
||||
if (lastCanvasSize != newCanvasSize)
|
||||
{
|
||||
lastCanvasSize = newCanvasSize;
|
||||
view.OnCanvasSizeChanged(newCanvasSize);
|
||||
}
|
||||
if (sender is SKMetalView 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,8 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
[UnsupportedOSPlatform("macos")]
|
||||
public partial class SKGLViewHandler : ViewHandler<ISKGLView, SKGLView>
|
||||
{
|
||||
private SKSizeI lastCanvasSize;
|
||||
private GRContext? lastGRContext;
|
||||
private SKTouchHandler? touchHandler;
|
||||
private PaintSurfaceProxy? paintSurfaceProxy;
|
||||
private SKTouchHandlerProxy? touchProxy;
|
||||
private RenderLoopManager? renderLoopManager;
|
||||
|
||||
protected override SKGLView CreatePlatformView() =>
|
||||
|
@ -30,22 +29,23 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
|
||||
protected override void ConnectHandler(SKGLView platformView)
|
||||
{
|
||||
paintSurfaceProxy = new();
|
||||
paintSurfaceProxy.Connect(VirtualView, platformView);
|
||||
touchProxy = new();
|
||||
touchProxy.Connect(VirtualView, platformView);
|
||||
renderLoopManager = new RenderLoopManager(this);
|
||||
|
||||
platformView.PaintSurface += OnPaintSurface;
|
||||
|
||||
base.ConnectHandler(platformView);
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SKGLView platformView)
|
||||
{
|
||||
paintSurfaceProxy?.Disconnect(platformView);
|
||||
paintSurfaceProxy = null;
|
||||
touchProxy?.Disconnect(platformView);
|
||||
touchProxy = null;
|
||||
renderLoopManager?.StopRenderLoop();
|
||||
|
||||
touchHandler?.Detach(platformView);
|
||||
touchHandler = null;
|
||||
|
||||
platformView.PaintSurface -= OnPaintSurface;
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
|
@ -75,49 +75,11 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
|
||||
public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view)
|
||||
{
|
||||
handler.touchHandler ??= new SKTouchHandler(
|
||||
args => view.OnTouch(args),
|
||||
(x, y) => handler.OnGetScaledCoord(x, y));
|
||||
|
||||
handler.touchHandler?.SetEnabled(handler.PlatformView, view.EnableTouchEvents);
|
||||
handler.touchProxy?.UpdateEnableTouchEvents(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; }
|
||||
|
@ -215,5 +177,41 @@ namespace SkiaSharp.Views.Maui.Handlers
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace SkiaSharp.Views.Maui.Platform;
|
||||
|
||||
internal class SKEventProxy<TVirtualView, TPlatformView>
|
||||
where TVirtualView : class
|
||||
where TPlatformView : class
|
||||
{
|
||||
private WeakReference<TVirtualView>? virtualView;
|
||||
|
||||
protected TVirtualView? VirtualView =>
|
||||
virtualView is not null && virtualView.TryGetTarget(out var v) ? v : null;
|
||||
|
||||
public void Connect(TVirtualView virtualView, TPlatformView platformView)
|
||||
{
|
||||
this.virtualView = new(virtualView);
|
||||
OnConnect(virtualView, platformView);
|
||||
}
|
||||
|
||||
protected virtual void OnConnect(TVirtualView virtualView, TPlatformView platformView)
|
||||
{
|
||||
}
|
||||
|
||||
public void Disconnect(TPlatformView platformView)
|
||||
{
|
||||
virtualView = null;
|
||||
OnDisconnect(platformView);
|
||||
}
|
||||
|
||||
protected virtual void OnDisconnect(TPlatformView platformView)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
using Microsoft.Maui;
|
||||
using System;
|
||||
using UIKit;
|
||||
|
||||
namespace SkiaSharp.Views.Maui.Platform;
|
||||
|
||||
internal class SKTouchHandlerProxy : SKEventProxy<IView, UIView>
|
||||
{
|
||||
private SKTouchHandler? touchHandler;
|
||||
|
||||
protected override void OnDisconnect(UIView platformView)
|
||||
{
|
||||
touchHandler?.Detach(platformView);
|
||||
touchHandler = null;
|
||||
}
|
||||
|
||||
public void UpdateEnableTouchEvents(UIView platformView, bool enabled)
|
||||
{
|
||||
if (VirtualView is null)
|
||||
return;
|
||||
|
||||
touchHandler ??= new SKTouchHandler(
|
||||
args => OnTouch(args),
|
||||
(x, y) => OnGetScaledCoord(x, y));
|
||||
|
||||
touchHandler?.SetEnabled(platformView, enabled);
|
||||
}
|
||||
|
||||
private void OnTouch(SKTouchEventArgs e)
|
||||
{
|
||||
if (VirtualView is ISKCanvasView canvasView)
|
||||
canvasView.OnTouch(e);
|
||||
else if (VirtualView is ISKGLView glView)
|
||||
glView.OnTouch(e);
|
||||
}
|
||||
|
||||
private SKPoint OnGetScaledCoord(double x, double y)
|
||||
{
|
||||
var ignore = false;
|
||||
if (VirtualView is ISKCanvasView canvasView)
|
||||
ignore = canvasView.IgnorePixelScaling;
|
||||
else if (VirtualView is ISKGLView glView)
|
||||
ignore = glView.IgnorePixelScaling;
|
||||
|
||||
if (ignore == false && touchHandler?.View is {} platformView)
|
||||
{
|
||||
var scale = platformView.ContentScaleFactor;
|
||||
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
}
|
||||
|
||||
return new SKPoint((float)x, (float)y);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32515.10
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Tests.Devices", "SkiaSharp.Tests.Devices\SkiaSharp.Tests.Devices.csproj", "{1675A562-6545-4FBE-8AF7-C07784D83944}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Tests", "SkiaSharp.Tests\SkiaSharp.Tests.csproj", "{1C63B836-2628-4365-8237-08080E76117B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp", "..\binding\SkiaSharp\SkiaSharp.csproj", "{9D753C4C-D7FC-4D1B-ABF0-BF1C089B987A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HarfBuzzSharp", "..\binding\HarfBuzzSharp\HarfBuzzSharp.csproj", "{D48557C5-795D-4948-84EE-A7531DDD91DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.SceneGraph", "..\binding\SkiaSharp.SceneGraph\SkiaSharp.SceneGraph.csproj", "{8CD906F8-B3E4-48E6-8B16-EAFC0C34EAE1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Resources", "..\binding\SkiaSharp.Resources\SkiaSharp.Resources.csproj", "{AD2C6978-4F5E-E592-B565-26C357877B2C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Skottie", "..\binding\SkiaSharp.Skottie\SkiaSharp.Skottie.csproj", "{915D1D57-B059-4301-9A35-2E5EB68DED99}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.HarfBuzz", "..\source\SkiaSharp.HarfBuzz\SkiaSharp.HarfBuzz\SkiaSharp.HarfBuzz.csproj", "{6F999CA5-B67F-46A3-9A94-9E99527060F6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Views", "..\source\SkiaSharp.Views\SkiaSharp.Views\SkiaSharp.Views.csproj", "{398936B0-1B68-4F2D-B91C-6880CAC9F168}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Views.Maui.Core", "..\source\SkiaSharp.Views.Maui\SkiaSharp.Views.Maui.Core\SkiaSharp.Views.Maui.Core.csproj", "{CB3FAE69-DE1F-47FF-A158-B0EF8F5F8AF6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Views.Maui.Controls", "..\source\SkiaSharp.Views.Maui\SkiaSharp.Views.Maui.Controls\SkiaSharp.Views.Maui.Controls.csproj", "{72C22D09-AC66-4D5A-B503-D7CDA2AD6A3B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source", "source", "{6779122B-72B0-42ED-A1E7-5029C1C0A78D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1675A562-6545-4FBE-8AF7-C07784D83944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1675A562-6545-4FBE-8AF7-C07784D83944}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1675A562-6545-4FBE-8AF7-C07784D83944}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{1675A562-6545-4FBE-8AF7-C07784D83944}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1675A562-6545-4FBE-8AF7-C07784D83944}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1675A562-6545-4FBE-8AF7-C07784D83944}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{1C63B836-2628-4365-8237-08080E76117B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1C63B836-2628-4365-8237-08080E76117B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1C63B836-2628-4365-8237-08080E76117B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1C63B836-2628-4365-8237-08080E76117B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9D753C4C-D7FC-4D1B-ABF0-BF1C089B987A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9D753C4C-D7FC-4D1B-ABF0-BF1C089B987A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9D753C4C-D7FC-4D1B-ABF0-BF1C089B987A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9D753C4C-D7FC-4D1B-ABF0-BF1C089B987A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D48557C5-795D-4948-84EE-A7531DDD91DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D48557C5-795D-4948-84EE-A7531DDD91DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D48557C5-795D-4948-84EE-A7531DDD91DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D48557C5-795D-4948-84EE-A7531DDD91DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8CD906F8-B3E4-48E6-8B16-EAFC0C34EAE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8CD906F8-B3E4-48E6-8B16-EAFC0C34EAE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8CD906F8-B3E4-48E6-8B16-EAFC0C34EAE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8CD906F8-B3E4-48E6-8B16-EAFC0C34EAE1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AD2C6978-4F5E-E592-B565-26C357877B2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AD2C6978-4F5E-E592-B565-26C357877B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AD2C6978-4F5E-E592-B565-26C357877B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AD2C6978-4F5E-E592-B565-26C357877B2C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{915D1D57-B059-4301-9A35-2E5EB68DED99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{915D1D57-B059-4301-9A35-2E5EB68DED99}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{915D1D57-B059-4301-9A35-2E5EB68DED99}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{915D1D57-B059-4301-9A35-2E5EB68DED99}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6F999CA5-B67F-46A3-9A94-9E99527060F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6F999CA5-B67F-46A3-9A94-9E99527060F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6F999CA5-B67F-46A3-9A94-9E99527060F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6F999CA5-B67F-46A3-9A94-9E99527060F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{398936B0-1B68-4F2D-B91C-6880CAC9F168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{398936B0-1B68-4F2D-B91C-6880CAC9F168}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{398936B0-1B68-4F2D-B91C-6880CAC9F168}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{398936B0-1B68-4F2D-B91C-6880CAC9F168}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CB3FAE69-DE1F-47FF-A158-B0EF8F5F8AF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CB3FAE69-DE1F-47FF-A158-B0EF8F5F8AF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CB3FAE69-DE1F-47FF-A158-B0EF8F5F8AF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB3FAE69-DE1F-47FF-A158-B0EF8F5F8AF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{72C22D09-AC66-4D5A-B503-D7CDA2AD6A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{72C22D09-AC66-4D5A-B503-D7CDA2AD6A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{72C22D09-AC66-4D5A-B503-D7CDA2AD6A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{72C22D09-AC66-4D5A-B503-D7CDA2AD6A3B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{9D753C4C-D7FC-4D1B-ABF0-BF1C089B987A} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{D48557C5-795D-4948-84EE-A7531DDD91DC} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{8CD906F8-B3E4-48E6-8B16-EAFC0C34EAE1} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{AD2C6978-4F5E-E592-B565-26C357877B2C} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{915D1D57-B059-4301-9A35-2E5EB68DED99} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{6F999CA5-B67F-46A3-9A94-9E99527060F6} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{398936B0-1B68-4F2D-B91C-6880CAC9F168} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{CB3FAE69-DE1F-47FF-A158-B0EF8F5F8AF6} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
{72C22D09-AC66-4D5A-B503-D7CDA2AD6A3B} = {6779122B-72B0-42ED-A1E7-5029C1C0A78D}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {34FFBC0E-9245-423A-9A91-687C9B7FDB8B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -3,6 +3,7 @@ using DeviceRunners.VisualRunners;
|
|||
using DeviceRunners.XHarness;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using SkiaSharp.Views.Maui.Controls.Hosting;
|
||||
|
||||
namespace SkiaSharp.Tests
|
||||
{
|
||||
|
@ -23,6 +24,7 @@ namespace SkiaSharp.Tests
|
|||
};
|
||||
|
||||
builder
|
||||
.UseSkiaSharp()
|
||||
.ConfigureUITesting()
|
||||
.UseXHarnessTestRunner(conf => conf
|
||||
.AddTestAssemblies(testAssemblies)
|
||||
|
|
|
@ -68,13 +68,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Tests\**" />
|
||||
<None Include="Tests\**" />
|
||||
<Compile Include="Tests\Apple\**\*.cs;Tests\iOS\**\*.cs" Condition="$(TargetFramework.Contains('-ios')) or $(TargetFramework.Contains('-maccatalyst')) or $(TargetFramework.Contains('-tvos'))" />
|
||||
<Compile Include="Tests\Apple\**\*.cs;Tests\macOS\**\*.cs" Condition="$(TargetFramework.Contains('-macos'))" />
|
||||
<Compile Include="Tests\Android\**\*.cs" Condition="$(TargetFramework.Contains('-android'))" />
|
||||
<Compile Include="Tests\Tizen\**\*.cs" Condition="$(TargetFramework.Contains('-tizen'))" />
|
||||
<Compile Include="Tests\Windows\**\*.cs" Condition="$(TargetFramework.Contains('-windows'))" />
|
||||
<_PlatformCompile Include="Tests\Apple\**\*.cs;Tests\iOS\**\*.cs" Condition="$(TargetFramework.Contains('-ios')) or $(TargetFramework.Contains('-maccatalyst')) or $(TargetFramework.Contains('-tvos'))" />
|
||||
<_PlatformCompile Include="Tests\Apple\**\*.cs;Tests\macOS\**\*.cs" Condition="$(TargetFramework.Contains('-macos'))" />
|
||||
<_PlatformCompile Include="Tests\Android\**\*.cs" Condition="$(TargetFramework.Contains('-android'))" />
|
||||
<_PlatformCompile Include="Tests\Tizen\**\*.cs" Condition="$(TargetFramework.Contains('-tizen'))" />
|
||||
<_PlatformCompile Include="Tests\Windows\**\*.cs" Condition="$(TargetFramework.Contains('-windows'))" />
|
||||
<_OtherCompile Include="Tests\Apple\**;Tests\iOS\**;Tests\macOS\**;Tests\Android\**;Tests\Tizen\**;Tests\Windows\**" Exclude="@(_PlatformCompile)" />
|
||||
<Compile Remove="@(_OtherCompile)" />
|
||||
<None Include="@(_OtherCompile)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\binding\IncludeNativeAssets.SkiaSharp.targets" />
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Xunit;
|
||||
|
||||
namespace SkiaSharp.Views.Maui.Controls.Tests;
|
||||
|
||||
public static class MauiExtensions
|
||||
{
|
||||
private static readonly Rect InitialFrame = new(0, 0, -1, -1);
|
||||
|
||||
public static async Task WaitForLoaded(
|
||||
this VisualElement element,
|
||||
int timeout = 1000)
|
||||
{
|
||||
if (element.IsLoaded)
|
||||
return;
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
element.Loaded += OnLoaded;
|
||||
|
||||
await Task.WhenAny(tcs.Task, Task.Delay(timeout));
|
||||
|
||||
element.Loaded -= OnLoaded;
|
||||
|
||||
Assert.True(element.IsLoaded);
|
||||
|
||||
void OnLoaded(object? sender, EventArgs e)
|
||||
{
|
||||
element.Loaded -= OnLoaded;
|
||||
tcs.SetResult();
|
||||
}
|
||||
}
|
||||
|
||||
public static Task WaitForLayout(
|
||||
this View view,
|
||||
Rect? initialFrame = default,
|
||||
int timeout = 1000,
|
||||
int interval = 100)
|
||||
{
|
||||
initialFrame ??= InitialFrame;
|
||||
return AssertEx.Eventually(
|
||||
() => view.Frame != initialFrame,
|
||||
timeout,
|
||||
interval);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Controls;
|
||||
using SkiaSharp.Tests;
|
||||
using Xunit;
|
||||
|
||||
namespace SkiaSharp.Views.Maui.Controls.Tests;
|
||||
|
||||
public class MemoryLeakTests : SKUITests
|
||||
{
|
||||
[UIFact]
|
||||
public Task SKCanvasViewHandlerDoesNotLeak() =>
|
||||
AssertHandlerDoesNotLeak(() =>
|
||||
{
|
||||
var view = new SKCanvasView();
|
||||
view.PaintSurface += (sender, e) =>
|
||||
{
|
||||
e.Surface.Canvas.Clear(SKColors.Red);
|
||||
};
|
||||
view.EnableTouchEvents = true;
|
||||
view.Touch += (sender, e) =>
|
||||
{
|
||||
view.InvalidateSurface();
|
||||
};
|
||||
return view;
|
||||
});
|
||||
|
||||
[UIFact]
|
||||
public Task SKGLViewHandlerDoesNotLeak() =>
|
||||
AssertHandlerDoesNotLeak(() =>
|
||||
{
|
||||
var view = new SKGLView();
|
||||
view.PaintSurface += (sender, e) =>
|
||||
{
|
||||
e.Surface.Canvas.Clear(SKColors.Red);
|
||||
};
|
||||
view.EnableTouchEvents = true;
|
||||
view.Touch += (sender, e) =>
|
||||
{
|
||||
view.InvalidateSurface();
|
||||
};
|
||||
view.HasRenderLoop = true;
|
||||
return view;
|
||||
});
|
||||
|
||||
private async Task AssertHandlerDoesNotLeak(Func<View> ctor)
|
||||
{
|
||||
async Task<(WeakReference, WeakReference, WeakReference)> RunTest()
|
||||
{
|
||||
var view = ctor();
|
||||
var page = new ContentPage
|
||||
{
|
||||
Content = view
|
||||
};
|
||||
|
||||
await CurrentPage.Navigation.PushAsync(page);
|
||||
|
||||
await view.WaitForLoaded();
|
||||
await view.WaitForLayout();
|
||||
|
||||
var viewReference = new WeakReference(view);
|
||||
var handlerReference = new WeakReference(view.Handler);
|
||||
var platformViewReference = new WeakReference(view.Handler.PlatformView);
|
||||
|
||||
await page.Navigation.PopAsync();
|
||||
|
||||
return (viewReference, handlerReference, platformViewReference);
|
||||
}
|
||||
var (viewRef, handlerRef, platformRef) = await RunTest();
|
||||
|
||||
await AssertEx.EventuallyGC(viewRef, handlerRef, platformRef);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Controls;
|
||||
using SkiaSharp.Tests;
|
||||
using Xunit;
|
||||
|
||||
namespace SkiaSharp.Views.Maui.Controls.Tests;
|
||||
|
||||
[Collection("SKUITests")]
|
||||
public abstract class SKUITests : SKTest, IAsyncLifetime
|
||||
{
|
||||
protected ContentPage CurrentPage { get; private set; } = null!;
|
||||
|
||||
protected IMauiContext MauiContext { get; private set; } = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
Routing.RegisterRoute("uitests", typeof(ContentPage));
|
||||
|
||||
await Shell.Current.GoToAsync("uitests");
|
||||
|
||||
CurrentPage = (ContentPage)Shell.Current.CurrentPage;
|
||||
|
||||
await CurrentPage.WaitForLoaded();
|
||||
|
||||
MauiContext = CurrentPage.Handler!.MauiContext!;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
// pop all modals
|
||||
while (Shell.Current.CurrentPage.Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await Shell.Current.CurrentPage.Navigation.PopModalAsync();
|
||||
}
|
||||
|
||||
// pop until we are back at our page
|
||||
while (Shell.Current.CurrentPage != CurrentPage)
|
||||
{
|
||||
await Shell.Current.CurrentPage.Navigation.PopAsync();
|
||||
}
|
||||
|
||||
CurrentPage = null!;
|
||||
|
||||
await Shell.Current.GoToAsync("..");
|
||||
|
||||
Routing.UnRegisterRoute("uitests");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Xunit;
|
||||
|
||||
public static class AssertEx
|
||||
{
|
||||
public static async Task Eventually(
|
||||
Func<bool> assertion,
|
||||
int timeout = 1000,
|
||||
int interval = 100,
|
||||
string message = "Assertion timed out")
|
||||
{
|
||||
do
|
||||
{
|
||||
if (assertion())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(interval);
|
||||
|
||||
timeout -= interval;
|
||||
}
|
||||
while (timeout >= 0);
|
||||
|
||||
if (!assertion())
|
||||
{
|
||||
throw new XunitException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task EventuallyGC(params WeakReference[] references)
|
||||
{
|
||||
Assert.NotEmpty(references);
|
||||
|
||||
bool AreReferencesCollected()
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
foreach (var reference in references)
|
||||
{
|
||||
Assert.NotNull(reference);
|
||||
if (reference.IsAlive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Eventually(AreReferencesCollected);
|
||||
}
|
||||
catch (XunitException ex)
|
||||
{
|
||||
throw new XunitException(ListLivingReferences(references), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ListLivingReferences(WeakReference[] references)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
foreach (var weakReference in references)
|
||||
{
|
||||
if (weakReference.IsAlive && weakReference.Target is object x)
|
||||
{
|
||||
stringBuilder.Append($"Reference to {x} (type {x.GetType()} is still alive.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче