Skip to content

Instantly share code, notes, and snippets.

@felipebaltazar
Created June 15, 2018 19:36
Show Gist options
  • Save felipebaltazar/73c15e6ffe1dd57ae7e09150308a71a2 to your computer and use it in GitHub Desktop.
Save felipebaltazar/73c15e6ffe1dd57ae7e09150308a71a2 to your computer and use it in GitHub Desktop.
using Draw.Core.Extensions;
using Draw.Math.Models;
using SkiaSharp.Views.Forms;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
using Ext = Draw.Core.Extensions.NumbersExtensions;
namespace Draw.Application.DrawSpace
{
public class Ambient2D : StackLayout
{
#region Internals
internal SKCanvasView CanvasView;
internal bool KeepSelection;
internal bool IsPanning;
internal bool IsZooning;
internal float CanvasScale = 1;
internal float WidthScreen = 0f;
internal float HeightScreen = 0f;
internal float StartCanvasScale = 1;
internal float TouchSensibility = 3f;
internal float MaximumZoomStep = 1.3f;
internal Vector2D LastTouch;
internal Vector2D TouchLocation = new Vector2D();
internal Vector2D CanvasPosition = new Vector2D();
internal Vector2D PreviousPosition = new Vector2D();
#endregion
#region Private properties values
private bool _isDrawing;
#endregion
#region Properties
public bool IsDrawing
{
get { return _isDrawing; }
set
{
if (_isDrawing == value)
return;
_isDrawing = value;
LastTouch = null;
OnPropertyChanged();
}
}
#endregion
#region Constructors
public Ambient2D()
{
BackgroundColor = Color.White;
VerticalOptions = LayoutOptions.FillAndExpand;
HorizontalOptions = LayoutOptions.FillAndExpand;
Padding = 0;
CreateView();
}
#endregion
#region Touch Methods
private void CanvasViewTouch(object sender, SKTouchEventArgs e)
{
if (TouchLocation == null)
TouchLocation = new Vector2D();
TouchLocation.X = e.Location.X / CanvasScale - CanvasPosition.X;
TouchLocation.Y = e.Location.Y / CanvasScale - CanvasPosition.Y;
}
internal virtual void DoubleTapGestureTapped(object sender)
{
CenterDraw();
StopCanvasActions();
}
internal virtual void SingleTapGestureTapped(object sender)
{
if (IsZooning)
return;
if (IsPanning)
{
PreviousPosition.X = 0;
PreviousPosition.Y = 0;
IsPanning = false;
}
if (!IsDrawing || TouchLocation == null)
{
LastTouch = null;
CanvasView?.InvalidateSurface();
return;
}
}
internal virtual void PanGesturePanUpdated(object sender, PanUpdatedEventArgs e)
{
if (e.StatusType == GestureStatus.Started)
{
PreviousPosition.X = e.TotalX.ToSingle();
PreviousPosition.Y = e.TotalY.ToSingle();
IsPanning = true;
}
else if (e.StatusType == GestureStatus.Running)
{
if (!IsPanning)
{
PreviousPosition.X = e.TotalX.ToSingle();
PreviousPosition.Y = e.TotalY.ToSingle();
StopCanvasActions();
IsPanning = true;
return;
}
CanvasPosition.X += (((e.TotalX - PreviousPosition.X) / CanvasScale)
* TouchSensibility).ToSingle();
CanvasPosition.Y += (((e.TotalY - PreviousPosition.Y))
* TouchSensibility / CanvasScale).ToSingle();
PreviousPosition.X = e.TotalX.ToSingle();
PreviousPosition.Y = e.TotalY.ToSingle();
CanvasView.InvalidateSurface();
}
else
{
PreviousPosition.X = 0;
PreviousPosition.Y = 0;
IsPanning = false;
}
}
internal virtual void PinchGesturePinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
IsZooning = true;
StartCanvasScale = CanvasScale;
var touchX = (CanvasView.CanvasSize.Width * e.ScaleOrigin.X).ToSingle();
var touchY = (CanvasView.CanvasSize.Height * e.ScaleOrigin.Y).ToSingle();
}
else if (e.Status == GestureStatus.Running)
{
var factor = (e.Scale - 1);
var delta = factor < 0 ? -1 : 1;
factor = Ext.Clamp((factor * delta), 0, MaximumZoomStep) * delta;
CanvasScale += ((factor * StartCanvasScale)).ToSingle();
CanvasScale = CanvasScale.Clamp(0.01f, 2);
CanvasView.InvalidateSurface();
StartCanvasScale = CanvasScale;
}
else
{
IsZooning = false;
}
}
#endregion
#region Private Methods
private void CreateView()
{
Children.Clear();
CanvasView = new SKCanvasView
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
EnableTouchEvents = true
};
CanvasView.PaintSurface += OnCanvasViewPaintSurface;
CanvasView.Touch += CanvasViewTouch;
CanvasView.EnableTouchEvents = true;
Children.Add(CanvasView);
var panGesture = new PanGestureRecognizer();
var pinchGesture = new PinchGestureRecognizer();
var singleTapGesture = new TapGestureRecognizer
{
Command = new Command((sender) => SingleTapGestureTapped(sender)),
NumberOfTapsRequired = 1
};
var doubleTapGesture = new TapGestureRecognizer
{
Command = new Command((sender) => DoubleTapGestureTapped(sender)),
NumberOfTapsRequired = 2
};
pinchGesture.PinchUpdated += PinchGesturePinchUpdated;
panGesture.PanUpdated += PanGesturePanUpdated;
GestureRecognizers.Add(pinchGesture);
GestureRecognizers.Add(panGesture);
GestureRecognizers.Add(doubleTapGesture);
GestureRecognizers.Add(singleTapGesture);
ResetZoom();
}
internal void StopCanvasActions()
{
IsZooning = false;
IsPanning = false;
}
#endregion
#region Public Methods
public void ResetZoom()
{
CanvasScale = 0.1f;
StartCanvasScale = CanvasScale;
CanvasView?.InvalidateSurface();
}
public void ResetLocation()
{
CanvasPosition.X = 0;
CanvasPosition.Y = 0;
CanvasView?.InvalidateSurface();
}
#endregion
#region Virtual Methods
internal virtual bool HasAntialias() =>
CanvasScale > 0.7f;
internal virtual void CenterDraw()
{
ResetLocation();
}
internal virtual void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
var info = args.Info;
var surface = args.Surface;
var canvas = surface.Canvas;
canvas.Scale(CanvasScale, CanvasScale);
canvas.Translate(CanvasPosition.X, CanvasPosition.Y);
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment