Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.ComponentModel;
using Android.Content;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;

using SKFormsView = SkiaSharp.Views.Forms.SKCanvasView;
using SKNativeView = SkiaSharp.Views.Android.SKCanvasView;
Expand All @@ -11,90 +7,7 @@

namespace SkiaSharp.Views.Forms
{
public class SKCanvasViewRenderer : ViewRenderer<SKFormsView, SKNativeView>
public class SKCanvasViewRenderer : SKCanvasViewRendererBase<SKFormsView, SKNativeView>
{
protected override void OnElementChanged(ElementChangedEventArgs<SKFormsView> e)
{
if (e.OldElement != null)
{
var oldController = (ISKCanvasViewController)e.OldElement;

// unsubscribe from events
oldController.SurfaceInvalidated -= OnSurfaceInvalidated;
oldController.GetCanvasSize -= OnGetCanvasSize;
}

if (e.NewElement != null)
{
var newController = (ISKCanvasViewController)e.NewElement;

// create the native view
var view = new InternalView(Context, newController);
view.IgnorePixelScaling = e.NewElement.IgnorePixelScaling;
SetNativeControl(view);

// subscribe to events from the user
newController.SurfaceInvalidated += OnSurfaceInvalidated;
newController.GetCanvasSize += OnGetCanvasSize;

// paint for the first time
Control.Invalidate();
}

base.OnElementChanged(e);
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);

if (e.PropertyName == nameof(SKFormsView.IgnorePixelScaling))
{
Control.IgnorePixelScaling = Element.IgnorePixelScaling;
}
}

protected override void Dispose(bool disposing)
{
// detach all events before disposing
var controller = (ISKCanvasViewController)Element;
if (controller != null)
{
controller.SurfaceInvalidated -= OnSurfaceInvalidated;
}

base.Dispose(disposing);
}

private void OnSurfaceInvalidated(object sender, EventArgs eventArgs)
{
// repaint the native control
Control.Invalidate();
}

// the user asked for the size
private void OnGetCanvasSize(object sender, GetCanvasSizeEventArgs e)
{
e.CanvasSize = Control?.CanvasSize ?? SKSize.Empty;
}

private class InternalView : SKNativeView
{
private readonly ISKCanvasViewController controller;

public InternalView(Context context, ISKCanvasViewController controller)
: base(context)
{
this.controller = controller;
}

protected override void OnDraw(SKSurface surface, SKImageInfo info)
{
base.OnDraw(surface, info);

// the control is being repainted, let the user know
controller.OnPaintSurface(new SKPaintSurfaceEventArgs(surface, info));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
using System;
using System.ComponentModel;
using Android.Content;
using Android.Opengl;
using Javax.Microedition.Khronos.Opengles;
using Android.Opengl;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

using SKFormsView = SkiaSharp.Views.Forms.SKGLView;
using SKNativeView = SkiaSharp.Views.Android.SKGLSurfaceView;
Expand All @@ -13,98 +8,18 @@

namespace SkiaSharp.Views.Forms
{
public class SKGLViewRenderer : ViewRenderer<SKFormsView, SKNativeView>
public class SKGLViewRenderer : SKGLViewRendererBase<SKFormsView, SKNativeView>
{
protected override void OnElementChanged(ElementChangedEventArgs<SKFormsView> e)
protected override void SetupRenderLoop(bool oneShot)
{
if (e.OldElement != null)
{
var oldController = (ISKGLViewController)e.OldElement;

// unsubscribe from events
oldController.SurfaceInvalidated -= OnSurfaceInvalidated;
oldController.GetCanvasSize -= OnGetCanvasSize;
}

if (e.NewElement != null)
{
var newController = (ISKGLViewController)e.NewElement;

// create the native view
var view = new SKNativeView(Context);
view.SetRenderer(new Renderer(newController));
SetNativeControl(view);

// subscribe to events from the user
newController.SurfaceInvalidated += OnSurfaceInvalidated;
newController.GetCanvasSize += OnGetCanvasSize;

// start the rendering
SetRenderMode();
}

base.OnElementChanged(e);
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);

// refresh the render loop
if (e.PropertyName == SKFormsView.HasRenderLoopProperty.PropertyName)
{
SetRenderMode();
}
}

protected override void Dispose(bool disposing)
{
// detach all events before disposing
var controller = (ISKGLViewController)Element;
if (controller != null)
{
controller.SurfaceInvalidated -= OnSurfaceInvalidated;
}

base.Dispose(disposing);
}

// the user asked to repaint
private void OnSurfaceInvalidated(object sender, EventArgs eventArgs)
{
// if we aren't in a loop, then refresh once
if (!Element.HasRenderLoop)
if (oneShot)
{
Control.RequestRender();
}
}

// the user asked for the size
private void OnGetCanvasSize(object sender, GetCanvasSizeEventArgs e)
{
e.CanvasSize = Control?.CanvasSize ?? SKSize.Empty;
}

private void SetRenderMode()
{
Control.RenderMode = Element.HasRenderLoop
? Rendermode.Continuously
: Rendermode.WhenDirty;
}

private class Renderer : SKNativeView.ISKRenderer
{
private readonly ISKGLViewController controller;

public Renderer(ISKGLViewController controller)
{
this.controller = controller;
}

public void OnDrawFrame(SKSurface surface, GRBackendRenderTargetDesc renderTarget)
{
controller.OnPaintSurface(new SKPaintGLSurfaceEventArgs(surface, renderTarget));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
</ProjectReference>
</ItemGroup>
<Import Project="..\SkiaSharp.Views.Forms.Shared\SkiaSharp.Views.Forms.Shared.projitems" Label="Shared" Condition="Exists('..\SkiaSharp.Views.Forms.Shared\SkiaSharp.Views.Forms.Shared.projitems')" />
<Import Project="..\SkiaSharp.Views.Forms.Native.Shared\SkiaSharp.Views.Forms.Native.Shared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.193\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.193\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Xamarin.Forms.targets')" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.ComponentModel;

using SKFormsView = SkiaSharp.Views.Forms.SKCanvasView;

#if __ANDROID__
using Xamarin.Forms.Platform.Android;
using SKNativeView = SkiaSharp.Views.Android.SKCanvasView;
using SKNativePaintSurfaceEventArgs = SkiaSharp.Views.Android.SKPaintSurfaceEventArgs;
#elif __IOS__
using Xamarin.Forms.Platform.iOS;
using SKNativeView = SkiaSharp.Views.iOS.SKCanvasView;
using SKNativePaintSurfaceEventArgs = SkiaSharp.Views.iOS.SKPaintSurfaceEventArgs;
#elif WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
using SKNativeView = SkiaSharp.Views.UWP.SKXamlCanvas;
using SKNativePaintSurfaceEventArgs = SkiaSharp.Views.UWP.SKPaintSurfaceEventArgs;
#endif

namespace SkiaSharp.Views.Forms
{
public abstract class SKCanvasViewRendererBase<TFormsView, TNativeView> : ViewRenderer<TFormsView, TNativeView>
where TFormsView : SKFormsView
where TNativeView : SKNativeView
{
protected override void OnElementChanged(ElementChangedEventArgs<TFormsView> e)
{
if (e.OldElement != null)
{
var oldController = (ISKCanvasViewController)e.OldElement;

// unsubscribe from events
oldController.SurfaceInvalidated -= OnSurfaceInvalidated;
oldController.GetCanvasSize -= OnGetCanvasSize;
}

if (Control != null)
{
var control = Control;
control.PaintSurface -= OnPaintSurface;
}

if (e.NewElement != null)
{
var newController = (ISKCanvasViewController)e.NewElement;

// create the native view
var view = CreateNativeControl();
view.IgnorePixelScaling = e.NewElement.IgnorePixelScaling;
view.PaintSurface += OnPaintSurface;
SetNativeControl(view);

// subscribe to events from the user
newController.SurfaceInvalidated += OnSurfaceInvalidated;
newController.GetCanvasSize += OnGetCanvasSize;

// paint for the first time
OnSurfaceInvalidated(newController, EventArgs.Empty);
}

base.OnElementChanged(e);
}

#if __ANDROID__
protected override TNativeView CreateNativeControl()
{
return (TNativeView)Activator.CreateInstance(typeof(TNativeView), new[] { Context });
}
#else
protected virtual TNativeView CreateNativeControl()
{
return (TNativeView)Activator.CreateInstance(typeof(TNativeView));
}
#endif

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);

if (e.PropertyName == nameof(SKFormsView.IgnorePixelScaling))
{
Control.IgnorePixelScaling = Element.IgnorePixelScaling;
}
}

protected override void Dispose(bool disposing)
{
// detach all events before disposing
var controller = (ISKCanvasViewController)Element;
if (controller != null)
{
controller.SurfaceInvalidated -= OnSurfaceInvalidated;
controller.GetCanvasSize -= OnGetCanvasSize;
}

var control = Control;
if (control != null)
{
control.PaintSurface -= OnPaintSurface;
}

base.Dispose(disposing);
}

private void OnPaintSurface(object sender, SKNativePaintSurfaceEventArgs e)
{
var controller = Element as ISKCanvasViewController;

// the control is being repainted, let the user know
controller?.OnPaintSurface(new SKPaintSurfaceEventArgs(e.Surface, e.Info));
}

private void OnSurfaceInvalidated(object sender, EventArgs eventArgs)
{
// repaint the native control
#if __IOS__
Control.SetNeedsDisplay();
#else
Control.Invalidate();
#endif
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our current beta, we had several crashes, because "OnSurfaceInvalidated" was called by a background thread.
Maybe it is better to post these on the main thread?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a bit tough... I think the two sides of the fence is to either let the user make sure that invalidate is done from the main/UI thread via Xamarin.Forms.Device.BeginInvokeOnMainThread(), or to let the framework do it automatically.

For most cases, the app will always be using the main thread, so this should not be an issue. Thus, the overhead for posting to the main thread on each call might be undesirable. However, if some event out of the user's control is causing this to be called, then we will have to wrap the invalidate.

In the case of your beta, are you able to determine if a call was being made from a background thread that you could control?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah of course. And I agree with you. We will apply the fix in our app logic ;-)


// the user asked for the size
private void OnGetCanvasSize(object sender, GetCanvasSizeEventArgs e)
{
e.CanvasSize = Control?.CanvasSize ?? SKSize.Empty;
}
}
}
Loading