From 96c5cd51d3c3812a9839e9dde0a15b1aff6464ad Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Tue, 9 Feb 2021 00:31:52 +0900 Subject: [PATCH] [Tizen] Add drag and drop gesture handlers --- .../DragGestureHandler.cs | 243 ++++++++++++++++++ .../DropGestureHandler.cs | 133 ++++++++++ .../Extensions/DragDropExtensions.cs | 114 ++++++++ .../GestureDetector.cs | 20 ++ .../Properties/AssemblyInfo.cs | 2 + .../Shapes/ShapeView.cs | 2 + .../SkiaSharp/CanvasViewRenderer.cs | 3 +- .../SkiaSharp/ICanvasRenderer.cs | 10 + 8 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 Xamarin.Forms.Platform.Tizen/DragGestureHandler.cs create mode 100644 Xamarin.Forms.Platform.Tizen/DropGestureHandler.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Extensions/DragDropExtensions.cs create mode 100644 Xamarin.Forms.Platform.Tizen/SkiaSharp/ICanvasRenderer.cs diff --git a/Xamarin.Forms.Platform.Tizen/DragGestureHandler.cs b/Xamarin.Forms.Platform.Tizen/DragGestureHandler.cs new file mode 100644 index 00000000000..dbdf310e1f4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/DragGestureHandler.cs @@ -0,0 +1,243 @@ +using System; +using System.Threading; +using ElmSharp; +using Tizen.Common; +using Xamarin.Forms.Platform.Tizen.SkiaSharp; +using EGestureType = ElmSharp.GestureLayer.GestureType; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class DragGestureHandler : GestureHandler + { + bool _isApi4; + + DragDropExtensions.Interop.DragIconCreateCallback _iconCallback; + DragDropExtensions.Interop.DragStateCallback _dragDoneCallback; + + static bool s_isDragging; + static CustomDragStateData s_currentDragStateData; + + public DragGestureHandler(IGestureRecognizer recognizer, IVisualElementRenderer renderer) : base(recognizer) + { + _iconCallback = OnIconCallback; + _dragDoneCallback = OnDragDoneCallback; + _isApi4 = DotnetUtil.TizenAPIVersion <= 4; + Renderer = renderer; + } + + public override EGestureType Type => EGestureType.LongTap; + + public IVisualElementRenderer Renderer { get; } + + public static CustomDragStateData CurrentStateData + { + get + { + return s_currentDragStateData; + } + } + + EvasObject NativeView + { + get + { + var native = Renderer.NativeView; + if (Renderer is SkiaSharp.ICanvasRenderer canvasRenderer) + { + native = canvasRenderer.RealNativeView; + } + return native; + } + } + + public void ResetCurrentStateData() + { + s_currentDragStateData = null; + } + + protected override void OnStarted(View sender, object data) + { + } + + protected override void OnMoved(View sender, object data) + { + //Workaround to prevent an error occuring by multiple StartDrag calling in Tizen 6.5 + if (!s_isDragging) + { + ResetCurrentStateData(); + StartDrag(); + } + } + + protected override void OnCompleted(View sender, object data) + { + } + + protected override void OnCanceled(View sender, object data) + { + } + + void StartDrag() + { + if (Recognizer is DragGestureRecognizer dragGestureRecognizer && dragGestureRecognizer.CanDrag) + { + if (Renderer == null) + return; + + var arg = dragGestureRecognizer.SendDragStarting(Renderer.Element); + + if (arg.Cancel) + return; + + s_currentDragStateData = new CustomDragStateData(); + s_currentDragStateData.DataPackage = arg.Data; + + var target = DragDropExtensions.DragDropContentType.Text; + var strData = string.IsNullOrEmpty(arg.Data.Text) ? " " : arg.Data.Text; + + s_isDragging = true; + + DragDropExtensions.StartDrag(NativeView, + target, + strData, + DragDropExtensions.DragDropActionType.Move, + _iconCallback, + null, + null, + _dragDoneCallback); + } + } + + IntPtr OnIconCallback(IntPtr data, IntPtr window, ref int xoff, ref int yoff) + { + EvasObject icon = null; + EvasObject parent = new CustomWindow(NativeView, window); + + if (s_currentDragStateData.DataPackage.Image != null) + { + icon = GetImageIcon(parent); + } + else if (NativeView is ShapeView) + { + icon = GetShapeView(parent); + } + else + { + icon = GetDefaultIcon(parent); + } + var bound = NativeView.Geometry; + bound.X = 0; + bound.Y = 0; + icon.Geometry = bound; + + if (icon is Native.Label) + { + icon.Resized += (s, e) => + { + var map = new EvasMap(4); + map.PopulatePoints(icon.Geometry, 0); + map.Zoom(0.5, 0.5, 0, 0); + icon.IsMapEnabled = true; + icon.EvasMap = map; + }; + } + else + { + var map = new EvasMap(4); + map.PopulatePoints(icon.Geometry, 0); + map.Zoom(0.5, 0.5, 0, 0); + icon.IsMapEnabled = true; + icon.EvasMap = map; + } + + + return icon; + } + + EvasObject GetDefaultIcon(EvasObject parent) + { + if (!string.IsNullOrEmpty(s_currentDragStateData.DataPackage.Text)) + { + var label = new Native.Label(parent); + label.Text = s_currentDragStateData.DataPackage.Text; + + if (Renderer.Element is Label lb) + label.FontSize = lb.FontSize; + else if (Renderer.Element is Entry et) + label.FontSize = et.FontSize; + else if (Renderer.Element is Editor ed) + label.FontSize = ed.FontSize; + + return label; + } + else + { + var box = new ElmSharp.Rectangle(parent); + box.Color = new ElmSharp.Color(128, 128, 128, 128); + return box; + } + } + + EvasObject GetImageIcon(EvasObject parent) + { + var image = new Native.Image(parent); + _ = image.LoadFromImageSourceAsync(s_currentDragStateData.DataPackage.Image); + return image; + } + + EvasObject GetShapeView(EvasObject parent) + { + var copiedImg = new EvasImage(parent); + copiedImg.IsFilled = true; + + if (NativeView is ShapeView shapeView) + { + var canvas = shapeView.SKCanvasView; + var realHandle = DragDropExtensions.Interop.elm_object_part_content_get(canvas, "elm.swallow.content"); + + DragDropExtensions.Interop.evas_object_image_size_get(realHandle, out int w, out int h); + DragDropExtensions.Interop.evas_object_image_size_set(copiedImg, w, h); + + var imgData = DragDropExtensions.Interop.evas_object_image_data_get(realHandle, false); + DragDropExtensions.Interop.evas_object_image_data_set(copiedImg, imgData); + } + + return copiedImg; + } + + void OnDragDoneCallback(IntPtr data, IntPtr obj) + { + s_isDragging = false; + if (Recognizer is DragGestureRecognizer dragGestureRecognizer && dragGestureRecognizer.CanDrag) + { + dragGestureRecognizer.SendDropCompleted(new DropCompletedEventArgs()); + } + } + + public class CustomWindow : EvasObject + { + IntPtr _handle; + + public CustomWindow(EvasObject parent, IntPtr handle) : base() + { + _handle = handle; + Realize(parent); + } + + public CustomWindow(EvasObject handle) : base(handle) + { + } + + protected override IntPtr CreateHandle(EvasObject parent) + { + return _handle; + } + } + + public class CustomDragStateData + { + public DataPackage DataPackage { get; set; } + public DataPackageOperation AcceptedOperation { get; set; } = DataPackageOperation.Copy; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Tizen/DropGestureHandler.cs b/Xamarin.Forms.Platform.Tizen/DropGestureHandler.cs new file mode 100644 index 00000000000..ac9c7d5c69a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/DropGestureHandler.cs @@ -0,0 +1,133 @@ +using System; +using System.Linq; +using ElmSharp; +using Tizen.Common; +using EGestureType = ElmSharp.GestureLayer.GestureType; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class DropGestureHandler : GestureHandler + { + bool _isApi4; + + DragDropExtensions.Interop.DragStateCallback _dragEnterCallback; + DragDropExtensions.Interop.DragStateCallback _dragLeaveCallback; + DragDropExtensions.Interop.DropCallback _dropCallback; + + public override EGestureType Type => default(EGestureType); + + public DropGestureHandler(IGestureRecognizer recognizer, IVisualElementRenderer renderer) : base(recognizer) + { + _dragEnterCallback = OnEnterCallback; + _dragLeaveCallback = OnLeaveCallback; + _dropCallback = OnDropCallback; + _isApi4 = DotnetUtil.TizenAPIVersion <= 4; + Renderer = renderer; + } + + public IVisualElementRenderer Renderer { get; } + + EvasObject NativeView + { + get + { + var native = Renderer.NativeView; + if (Renderer is SkiaSharp.ICanvasRenderer canvasRenderer) + { + native = canvasRenderer.RealNativeView; + } + + if (native is Native.Canvas canvas) + { + var child = canvas.Children.LastOrDefault(); + + if (child != null) + { + if (child.PassEvents) + child.PassEvents = false; + + return child; + } + } + return native; + } + } + + + public void AddDropGesture() + { + if (Renderer == null) + return; + + var target = DragDropExtensions.DragDropContentType.Targets; + + DragDropExtensions.AddDropTarget(NativeView, + target, + _dragEnterCallback, + _dragLeaveCallback, null, + _dropCallback); + } + + void OnEnterCallback(IntPtr data, IntPtr obj) + { + var currentStateData = DragGestureHandler.CurrentStateData; + if (currentStateData == null) + return; + + var arg = new DragEventArgs(currentStateData.DataPackage); + + if (Recognizer is DropGestureRecognizer dropRecognizer && dropRecognizer.AllowDrop) + dropRecognizer.SendDragOver(arg); + + DragGestureHandler.CurrentStateData.AcceptedOperation = arg.AcceptedOperation; + } + + void OnLeaveCallback(IntPtr data, IntPtr obj) + { + var currentStateData = DragGestureHandler.CurrentStateData; + if (currentStateData == null) + return; + + var arg = new DragEventArgs(currentStateData.DataPackage); + + if (Recognizer is DropGestureRecognizer dropRecognizer && dropRecognizer.AllowDrop) + dropRecognizer.SendDragLeave(arg); + + DragGestureHandler.CurrentStateData.AcceptedOperation = arg.AcceptedOperation; + } + + bool OnDropCallback(IntPtr data, IntPtr obj, IntPtr selectionData) + { + var currentStateData = DragGestureHandler.CurrentStateData; + + if (currentStateData.DataPackage == null || currentStateData.AcceptedOperation == DataPackageOperation.None) + return false; + + Device.BeginInvokeOnMainThread(async ()=> + { + if (Recognizer is DropGestureRecognizer dropRecognizer && dropRecognizer.AllowDrop) + await dropRecognizer.SendDrop(new DropEventArgs(currentStateData.DataPackage.View)); + }); + + return true; + } + + #region GestureHandler + protected override void OnStarted(View sender, object data) + { + } + + protected override void OnMoved(View sender, object data) + { + } + + protected override void OnCompleted(View sender, object data) + { + } + + protected override void OnCanceled(View sender, object data) + { + } + #endregion + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/DragDropExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/DragDropExtensions.cs new file mode 100644 index 00000000000..98b05b421e9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/DragDropExtensions.cs @@ -0,0 +1,114 @@ +using System; +using System.Runtime.InteropServices; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class DragDropExtensions + { + public static void AddDropTarget(EvasObject obj, DragDropContentType contentType, + Interop.DragStateCallback enterCallback, + Interop.DragStateCallback leaveCallback, + Interop.DragPositionCallback positionCallback, + Interop.DropCallback dropCallback) + { + Interop.elm_drop_target_add(obj.RealHandle, contentType, + enterCallback, IntPtr.Zero, + leaveCallback, IntPtr.Zero, + positionCallback, IntPtr.Zero, + dropCallback, IntPtr.Zero); + } + + public static void StartDrag(EvasObject obj, DragDropContentType contentType, + string data, DragDropActionType actionType, + Interop.DragIconCreateCallback iconCallback, + Interop.DragPositionCallback positionCallback, + Interop.DragAcceptCallback acceptCallback, + Interop.DragStateCallback statCallback) + { + var strData = Marshal.StringToHGlobalAnsi(data); + Interop.elm_drag_start(obj.RealHandle, contentType, strData, actionType, + iconCallback, IntPtr.Zero, + positionCallback, IntPtr.Zero, + acceptCallback, IntPtr.Zero, + statCallback, IntPtr.Zero); + } + + public enum DragDropContentType + { + Targets = -1, + None = 0, + Text = 1, + MarkUp = 2, + Image = 4, + VCard = 8, + Html = 16 + } + + public enum DragDropActionType + { + Unknown = 0, + Copy, + Move, + Private, + Ask, + List, + Link, + Description + } + + public class Interop + { + public const string LibElementary = "libelementary.so.1"; + public const string LibEvas = "libevas.so.1"; + + + public delegate IntPtr DragIconCreateCallback(IntPtr data, IntPtr window, ref int xoff, ref int yoff); + public delegate void DragPositionCallback(IntPtr data, IntPtr obj, int x, int y, int actionType); + public delegate void DragAcceptCallback(IntPtr data, IntPtr obj, bool accept); + public delegate void DragStateCallback(IntPtr data, IntPtr obj); + public delegate bool DropCallback(IntPtr data, IntPtr obj, IntPtr selectionData); + + [DllImport(LibElementary)] + internal static extern void elm_drop_target_add(IntPtr obj, + DragDropContentType type, + DragStateCallback enterCallback, + IntPtr enterData, + DragStateCallback leaveCallback, + IntPtr leaveData, + DragPositionCallback positionCallback, + IntPtr positionData, + DropCallback dropcallback, + IntPtr dropData); + + [DllImport(LibElementary)] + internal static extern void elm_drag_start(IntPtr obj, + DragDropContentType contentType, + IntPtr data, + DragDropActionType actionType, + DragIconCreateCallback iconCreateCallback, + IntPtr iconCreateData, + DragPositionCallback dragPositionCallback, + IntPtr dragPositonData, + DragAcceptCallback dragAcceptCallback, + IntPtr dragAcceptData, + DragStateCallback dragStateCallback, + IntPtr dragStateData); + + [DllImport(LibElementary)] + internal static extern IntPtr elm_object_part_content_get(IntPtr obj, string part); + + [DllImport(LibEvas)] + internal static extern IntPtr evas_object_image_data_get(IntPtr obj, bool forWriting); + + [DllImport(LibEvas)] + internal static extern void evas_object_image_data_set(IntPtr obj, IntPtr data); + + [DllImport(LibEvas)] + internal static extern void evas_object_image_size_get(IntPtr obj, out int w, out int h); + + [DllImport(LibEvas)] + internal static extern void evas_object_image_size_set(IntPtr obj, int w, int h); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/GestureDetector.cs b/Xamarin.Forms.Platform.Tizen/GestureDetector.cs index c8ead146d03..daaed1fb8ef 100644 --- a/Xamarin.Forms.Platform.Tizen/GestureDetector.cs +++ b/Xamarin.Forms.Platform.Tizen/GestureDetector.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using ElmSharp; @@ -173,6 +174,11 @@ void AddGesture(IGestureRecognizer recognizer) break; } } + + if (handler is DropGestureHandler dropGestureHandler) + { + dropGestureHandler.AddDropGesture(); + } } void RemoveGesture(IGestureRecognizer recognizer) @@ -260,6 +266,7 @@ void AddLongTapGesture(EGestureType type, double timeout) _gestureLayer.LongTapTimeout = timeout; _gestureLayer.SetTapCallback(type, GestureLayer.GestureState.Start, (data) => { OnLongTapStarted(type, data); }); + _gestureLayer.SetTapCallback(type, GestureLayer.GestureState.Move, (data) => { OnLongTapMoved(type, data); }); _gestureLayer.SetTapCallback(type, GestureLayer.GestureState.End, (data) => { OnLongTapCompleted(type, data); }); _gestureLayer.SetTapCallback(type, GestureLayer.GestureState.Abort, (data) => { OnGestureCanceled(type, data); }); } @@ -458,6 +465,11 @@ void OnLongTapStarted(EGestureType type, object data) OnGestureStarted(type, data); } + void OnLongTapMoved(EGestureType type, object data) + { + OnGestureMoved(type, data); + } + void OnLongTapCompleted(EGestureType type, object data) { _longTapTime = ((GestureLayer.TapData)data).Timestamp - _longTapTime; @@ -495,6 +507,14 @@ GestureHandler CreateHandler(IGestureRecognizer recognizer) { return new SwipeGestureHandler(recognizer); } + else if (recognizer is DragGestureRecognizer) + { + return new DragGestureHandler(recognizer, _renderer); + } + else if (recognizer is DropGestureRecognizer) + { + return new DropGestureHandler(recognizer, _renderer); + } return Forms.GetHandlerForObject(recognizer, recognizer); } diff --git a/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs index af12a805ad6..a1da42fd6cf 100644 --- a/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs @@ -65,6 +65,8 @@ [assembly: ExportHandler(typeof(PinchGestureRecognizer), typeof(PinchGestureHandler))] [assembly: ExportHandler(typeof(PanGestureRecognizer), typeof(PanGestureHandler))] [assembly: ExportHandler(typeof(SwipeGestureRecognizer), typeof(SwipeGestureHandler))] +[assembly: ExportHandler(typeof(DragGestureRecognizer), typeof(DragGestureHandler))] +[assembly: ExportHandler(typeof(DropGestureRecognizer), typeof(DropGestureHandler))] [assembly: ExportRenderer(typeof(Shell), typeof(Xamarin.Forms.Platform.Tizen.Watch.ShellRenderer), TargetIdiom.Watch)] diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs b/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs index 2f31ead7dd4..49d16e58b19 100644 --- a/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs +++ b/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs @@ -39,6 +39,8 @@ public ShapeView() : base(Forms.NativeParent) _stretch = Stretch.None; } + public SKCanvasView SKCanvasView => _skCanvasView; + void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) { var canvas = e.Surface.Canvas; diff --git a/Xamarin.Forms.Platform.Tizen/SkiaSharp/CanvasViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/SkiaSharp/CanvasViewRenderer.cs index 8b172c1b599..88a6c683d85 100644 --- a/Xamarin.Forms.Platform.Tizen/SkiaSharp/CanvasViewRenderer.cs +++ b/Xamarin.Forms.Platform.Tizen/SkiaSharp/CanvasViewRenderer.cs @@ -7,7 +7,7 @@ namespace Xamarin.Forms.Platform.Tizen.SkiaSharp { - public abstract class CanvasViewRenderer : ViewRenderer, IBackgroundCanvas, IClipperCanvas + public abstract class CanvasViewRenderer : ViewRenderer, IBackgroundCanvas, IClipperCanvas, ICanvasRenderer where TView : View where TNativeView : EvasObject { @@ -82,7 +82,6 @@ protected override void UpdateLayout() ClipperCanvas.Geometry = Control.Geometry; ClipperCanvas.Invalidate(); } - } protected void SetRealNativeControl(TNativeView control) diff --git a/Xamarin.Forms.Platform.Tizen/SkiaSharp/ICanvasRenderer.cs b/Xamarin.Forms.Platform.Tizen/SkiaSharp/ICanvasRenderer.cs new file mode 100644 index 00000000000..4ee03e12461 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/SkiaSharp/ICanvasRenderer.cs @@ -0,0 +1,10 @@ +using ElmSharp; +using SkiaSharp.Views.Tizen; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public interface ICanvasRenderer + { + public EvasObject RealNativeView { get; } + } +} \ No newline at end of file