From edf6b12c4d7c9ba6844201f6c29f83b9ac9d4b1d Mon Sep 17 00:00:00 2001 From: Florian Sundermann Date: Sat, 3 Mar 2018 11:28:21 +0100 Subject: [PATCH 01/32] first bits of drag+drop support for osx --- src/Avalonia.Controls/DragDrop/DataFormats.cs | 15 ++++ .../DragDrop/DragOperation.cs | 13 ++++ src/Avalonia.Controls/DragDrop/IDragData.cs | 15 ++++ .../DragDrop/IDragDispatcher.cs | 12 +++ .../Avalonia.MonoMac/Avalonia.MonoMac.csproj | 4 +- src/OSX/Avalonia.MonoMac/DraggingInfo.cs | 63 +++++++++++++++ src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 76 +++++++++++++++++++ 7 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Controls/DragDrop/DataFormats.cs create mode 100644 src/Avalonia.Controls/DragDrop/DragOperation.cs create mode 100644 src/Avalonia.Controls/DragDrop/IDragData.cs create mode 100644 src/Avalonia.Controls/DragDrop/IDragDispatcher.cs create mode 100644 src/OSX/Avalonia.MonoMac/DraggingInfo.cs diff --git a/src/Avalonia.Controls/DragDrop/DataFormats.cs b/src/Avalonia.Controls/DragDrop/DataFormats.cs new file mode 100644 index 0000000000..f46651ed3b --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DataFormats.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Controls.DragDrop +{ + public static class DataFormats + { + /// + /// Dataformat for plaintext + /// + public static string Text = nameof(Text); + + /// + /// Dataformat for one or more filenames + /// + public static string FileNames = nameof(FileNames); + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/DragOperation.cs b/src/Avalonia.Controls/DragDrop/DragOperation.cs new file mode 100644 index 0000000000..ffc6f666f7 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DragOperation.cs @@ -0,0 +1,13 @@ +using System; + +namespace Avalonia.Controls.DragDrop +{ + [Flags] + public enum DragOperation + { + None = 0, + Copy = 1, + Move = 2, + Link = 4, + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/IDragData.cs b/src/Avalonia.Controls/DragDrop/IDragData.cs new file mode 100644 index 0000000000..b6dc53d32d --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/IDragData.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Avalonia.Controls.DragDrop +{ + public interface IDragData + { + IEnumerable GetDataFormats(); + + bool Contains(string dataFormat); + + string GetText(); + + IEnumerable GetFileNames(); + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs new file mode 100644 index 0000000000..5c39635dd0 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs @@ -0,0 +1,12 @@ +using Avalonia.Input; + +namespace Avalonia.Controls.DragDrop +{ + public interface IDragDispatcher + { + DragOperation DragEnter(IInputRoot inputRoot, Point point, IDragData data, DragOperation operation); + DragOperation DragOver(IInputRoot inputRoot, Point point, IDragData data, DragOperation operation); + void DragLeave(IInputRoot inputRoot); + DragOperation Drop(IInputRoot inputRoot, Point point, IDragData data, DragOperation operation); + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj index 5f6be91571..c31c131ea9 100644 --- a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj +++ b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 True @@ -19,4 +19,4 @@ - + \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs new file mode 100644 index 0000000000..3abcb8c68c --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using Avalonia.Controls.DragDrop; +using MonoMac.AppKit; + +namespace Avalonia.MonoMac +{ + class DraggingInfo : IDragData + { + private readonly NSDraggingInfo _info; + + public DraggingInfo(NSDraggingInfo info) + { + _info = info; + } + + + internal static NSDragOperation ConvertDragOperation(DragOperation d) + { + NSDragOperation result = NSDragOperation.None; + if (d.HasFlag(DragOperation.Copy)) + result |= NSDragOperation.Copy; + if (d.HasFlag(DragOperation.Link)) + result |= NSDragOperation.Link; + if (d.HasFlag(DragOperation.Move)) + result |= NSDragOperation.Move; + return result; + } + + internal static DragOperation ConvertDragOperation(NSDragOperation d) + { + DragOperation result = DragOperation.None; + if (d.HasFlag(NSDragOperation.Copy)) + result |= DragOperation.Copy; + if (d.HasFlag(NSDragOperation.Link)) + result |= DragOperation.Link; + if (d.HasFlag(NSDragOperation.Move)) + result |= DragOperation.Move; + return result; + } + + public Point Location => new Point(_info.DraggingLocation.X, _info.DraggingLocation.Y); + + public IEnumerable GetDataFormats() + { + yield break; + } + + public string GetText() + { + return null; + } + + public IEnumerable GetFileNames() + { + yield break; + } + + public bool Contains(string dataFormat) + { + return false; + } + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 667ee12fa0..91e822f802 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Avalonia.Controls.DragDrop; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; @@ -36,6 +37,7 @@ namespace Avalonia.MonoMac bool _isLeftPressed, _isRightPressed, _isMiddlePressed; private readonly IMouseDevice _mouse; private readonly IKeyboardDevice _keyboard; + private readonly IDragDispatcher _dragDispatcher; private NSTrackingArea _area; private NSCursor _cursor; private bool _nonUiRedrawQueued; @@ -52,6 +54,11 @@ namespace Avalonia.MonoMac _tl = tl; _mouse = AvaloniaLocator.Current.GetService(); _keyboard = AvaloniaLocator.Current.GetService(); + _dragDispatcher = AvaloniaLocator.Current.GetService(); + + RegisterForDraggedTypes(new string[] { + "public.data" // register for any kind of data. + }); } protected override void Dispose(bool disposing) @@ -144,6 +151,75 @@ namespace Avalonia.MonoMac UpdateCursor(); } + public override NSDragOperation DraggingEntered(NSDraggingInfo sender) + { + IInputRoot root = _tl?.InputRoot; + if (root == null || _dragDispatcher == null) + return NSDragOperation.None; + + var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); + DraggingInfo info = new DraggingInfo(sender); + var pt = TranslateLocalPoint(info.Location); + + dragOp = _dragDispatcher.DragEnter(root, pt, info, dragOp); + + return DraggingInfo.ConvertDragOperation(dragOp); + } + + public override NSDragOperation DraggingUpdated(NSDraggingInfo sender) + { + IInputRoot root = _tl?.InputRoot; + if (root == null || _dragDispatcher == null) + return NSDragOperation.None; + + var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); + DraggingInfo info = new DraggingInfo(sender); + var pt = TranslateLocalPoint(info.Location); + + dragOp = _dragDispatcher.DragOver(root, pt, info, dragOp); + + return DraggingInfo.ConvertDragOperation(dragOp); + } + + public override void DraggingExited(NSDraggingInfo sender) + { + IInputRoot root = _tl?.InputRoot; + if (root == null || _dragDispatcher == null) + return; + _dragDispatcher.DragLeave(root); + } + + public override bool PrepareForDragOperation(NSDraggingInfo sender) + { + IInputRoot root = _tl?.InputRoot; + if (root == null || _dragDispatcher == null) + return false; + + var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); + DraggingInfo info = new DraggingInfo(sender); + var pt = TranslateLocalPoint(info.Location); + + dragOp = _dragDispatcher.DragOver(root, pt, info, dragOp); + + return DraggingInfo.ConvertDragOperation(dragOp) != DragOperation.None; + } + + public override bool PerformDragOperation(NSDraggingInfo sender) + { + IInputRoot root = _tl?.InputRoot; + if (root == null || _dragDispatcher == null) + return false; + + var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); + DraggingInfo info = new DraggingInfo(sender); + var pt = TranslateLocalPoint(info.Location); + + dragOp = _dragDispatcher.Drop(root, pt, info, dragOp); + + return DraggingInfo.ConvertDragOperation(dragOp) != DragOperation.None; + } + + public override void SetFrameSize(CGSize newSize) { lock (SyncRoot) From 9ae9f6d2218de3bbde789fb044f22b773b31177d Mon Sep 17 00:00:00 2001 From: Florian Sundermann Date: Sat, 3 Mar 2018 12:40:06 +0100 Subject: [PATCH 02/32] raise routed events on drag and drop --- src/Avalonia.Controls/Application.cs | 4 +- .../DragDrop/DefaultDragDispatcher.cs | 90 +++++++++++++++++++ src/Avalonia.Controls/DragDrop/DragDrop.cs | 24 +++++ .../DragDrop/DragEventArgs.cs | 18 ++++ .../DragDrop/IDragDispatcher.cs | 11 ++- .../Properties/AssemblyInfo.cs | 1 + src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 4 +- 7 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs create mode 100644 src/Avalonia.Controls/DragDrop/DragDrop.cs create mode 100644 src/Avalonia.Controls/DragDrop/DragEventArgs.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 06c1a8b4cc..63b17530ff 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -12,6 +12,7 @@ using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; using System.Reactive.Concurrency; +using Avalonia.Controls.DragDrop; namespace Avalonia { @@ -234,7 +235,8 @@ namespace Avalonia .Bind().ToConstant(_styler) .Bind().ToSingleton() .Bind().ToConstant(this) - .Bind().ToConstant(AvaloniaScheduler.Instance); + .Bind().ToConstant(AvaloniaScheduler.Instance) + .Bind().ToConstant(DefaultDragDispatcher.Instance); } } } diff --git a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs new file mode 100644 index 0000000000..2ce5a32dc5 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs @@ -0,0 +1,90 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; +using System.Linq; + +namespace Avalonia.Controls.DragDrop +{ + class DefaultDragDispatcher : IDragDispatcher + { + public static readonly DefaultDragDispatcher Instance = new DefaultDragDispatcher(); + + private Interactive _lastTarget = null; + + private DefaultDragDispatcher() + { + } + + private Interactive GetTarget(IInputElement root, Point local) + { + var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); + if (target != null && DragDrop.GetAcceptDrag(target)) + return target; + return null; + } + + private DragOperation RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragOperation operation, IDragData data) + { + if (target == null) + return DragOperation.None; + var args = new DragEventArgs(routedEvent, data) + { + RoutedEvent = routedEvent, + DragOperation = operation + }; + target.RaiseEvent(args); + return args.DragOperation; + } + + public DragOperation DragEnter(IInputElement inputRoot, Point point, IDragData data, DragOperation operation) + { + _lastTarget = GetTarget(inputRoot, point); + return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, operation, data); + } + + public DragOperation DragOver(IInputElement inputRoot, Point point, IDragData data, DragOperation operation) + { + var target = GetTarget(inputRoot, point); + + if (target == _lastTarget) + return RaiseDragEvent(target, DragDrop.DragOverEvent, operation, data); + + try + { + if (_lastTarget != null) + _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + return RaiseDragEvent(target, DragDrop.DragEnterEvent, operation, data); + } + finally + { + _lastTarget = target; + } + } + + public void DragLeave(IInputElement inputRoot) + { + if (_lastTarget == null) + return; + try + { + _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + } + finally + { + _lastTarget = null; + } + } + + public DragOperation Drop(IInputElement inputRoot, Point point, IDragData data, DragOperation operation) + { + try + { + return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, operation, data); + } + finally + { + _lastTarget = null; + } + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/DragDrop.cs b/src/Avalonia.Controls/DragDrop/DragDrop.cs new file mode 100644 index 0000000000..3645409a5a --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DragDrop.cs @@ -0,0 +1,24 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls.DragDrop +{ + public sealed class DragDrop : AvaloniaObject + { + public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); + public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); + public static RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); + + public static AvaloniaProperty AcceptDragProperty = AvaloniaProperty.RegisterAttached("AcceptDrag", typeof(DragDrop), inherits: true); + + public static bool GetAcceptDrag(Interactive interactive) + { + return interactive.GetValue(AcceptDragProperty); + } + + public static void SetAcceptDrag(Interactive interactive, bool value) + { + interactive.SetValue(AcceptDragProperty, value); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs b/src/Avalonia.Controls/DragDrop/DragEventArgs.cs new file mode 100644 index 0000000000..e8bd2528c7 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DragEventArgs.cs @@ -0,0 +1,18 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls.DragDrop +{ + public class DragEventArgs : RoutedEventArgs + { + public DragOperation DragOperation { get; set; } + + public IDragData Data { get; private set; } + + public DragEventArgs(RoutedEvent routedEvent, IDragData data) + : base(routedEvent) + { + this.Data = data; + } + + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs index 5c39635dd0..05a211ff76 100644 --- a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs +++ b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs @@ -2,11 +2,14 @@ namespace Avalonia.Controls.DragDrop { + /// + /// Dispatches Drag+Drop events to the correct visual targets, based on the input root and the drag location. + /// public interface IDragDispatcher { - DragOperation DragEnter(IInputRoot inputRoot, Point point, IDragData data, DragOperation operation); - DragOperation DragOver(IInputRoot inputRoot, Point point, IDragData data, DragOperation operation); - void DragLeave(IInputRoot inputRoot); - DragOperation Drop(IInputRoot inputRoot, Point point, IDragData data, DragOperation operation); + DragOperation DragEnter(IInputElement inputRoot, Point point, IDragData data, DragOperation operation); + DragOperation DragOver(IInputElement inputRoot, Point point, IDragData data, DragOperation operation); + void DragLeave(IInputElement inputRoot); + DragOperation Drop(IInputElement inputRoot, Point point, IDragData data, DragOperation operation); } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Properties/AssemblyInfo.cs b/src/Avalonia.Controls/Properties/AssemblyInfo.cs index ae8c88f7e8..b0877e0fb7 100644 --- a/src/Avalonia.Controls/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls/Properties/AssemblyInfo.cs @@ -11,6 +11,7 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.DragDrop")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Embedding")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Presenters")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 91e822f802..711f6d2787 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -201,7 +201,7 @@ namespace Avalonia.MonoMac dragOp = _dragDispatcher.DragOver(root, pt, info, dragOp); - return DraggingInfo.ConvertDragOperation(dragOp) != DragOperation.None; + return dragOp != DragOperation.None; } public override bool PerformDragOperation(NSDraggingInfo sender) @@ -216,7 +216,7 @@ namespace Avalonia.MonoMac dragOp = _dragDispatcher.Drop(root, pt, info, dragOp); - return DraggingInfo.ConvertDragOperation(dragOp) != DragOperation.None; + return dragOp != DragOperation.None; } From a6e8dc0ffc79d00cb635109d6c3d70b6d105fa49 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sat, 3 Mar 2018 13:48:24 +0100 Subject: [PATCH 03/32] implemented IDragData for OSX --- src/OSX/Avalonia.MonoMac/DraggingInfo.cs | 30 +++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs index 3abcb8c68c..8164470548 100644 --- a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -1,11 +1,14 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Avalonia.Controls.DragDrop; using MonoMac.AppKit; +using MonoMac.Foundation; namespace Avalonia.MonoMac { class DraggingInfo : IDragData - { + { private readonly NSDraggingInfo _info; public DraggingInfo(NSDraggingInfo info) @@ -42,22 +45,37 @@ namespace Avalonia.MonoMac public IEnumerable GetDataFormats() { - yield break; + return _info.DraggingPasteboard.Types.Select(NSTypeToWellknownType); + } + + private string NSTypeToWellknownType(string type) + { + if (type == NSPasteboard.NSStringType) + return DataFormats.Text; + if (type == NSPasteboard.NSFilenamesType) + return DataFormats.FileNames; + return type; } public string GetText() { - return null; + return _info.DraggingPasteboard.GetStringForType(NSPasteboard.NSStringType); } public IEnumerable GetFileNames() { - yield break; + using(var fileNames = (NSArray)_info.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType)) + { + if (fileNames != null) + return NSArray.StringArrayFromHandle(fileNames.Handle); + } + + return Enumerable.Empty(); } public bool Contains(string dataFormat) { - return false; + return GetDataFormats().Any(f => f == dataFormat); } } } \ No newline at end of file From 1647d95aa6a192dc6978fe264d0b6422a83ba632 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sat, 3 Mar 2018 16:17:45 +0100 Subject: [PATCH 04/32] Initial Drag+Drop support for windows --- .../Avalonia.Win32/ClipboardFormats.cs | 80 ++++++++++ .../Interop/UnmanagedMethods.cs | 71 ++++++++- src/Windows/Avalonia.Win32/OleContext.cs | 47 ++++++ src/Windows/Avalonia.Win32/OleDataObject.cs | 141 ++++++++++++++++++ src/Windows/Avalonia.Win32/OleDropTarget.cs | 131 ++++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 10 ++ 6 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 src/Windows/Avalonia.Win32/ClipboardFormats.cs create mode 100644 src/Windows/Avalonia.Win32/OleContext.cs create mode 100644 src/Windows/Avalonia.Win32/OleDataObject.cs create mode 100644 src/Windows/Avalonia.Win32/OleDropTarget.cs diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs new file mode 100644 index 0000000000..254facf81a --- /dev/null +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using Avalonia.Controls.DragDrop; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + static class ClipboardFormats + { + class ClipboardFormat + { + public short Format { get; private set; } + public string Name { get; private set; } + + public ClipboardFormat(string name, short format) + { + Format = format; + Name = name; + } + } + + private static readonly List FormatList = new List() + { + new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT), + new ClipboardFormat(DataFormats.FileNames, (short)UnmanagedMethods.ClipboardFormat.CF_HDROP), + }; + + + private static string QueryFormatName(short format) + { + int len = UnmanagedMethods.GetClipboardFormatName(format, null, 0); + if (len > 0) + { + StringBuilder sb = new StringBuilder(len); + if (UnmanagedMethods.GetClipboardFormatName(format, sb, len) <= len) + return sb.ToString(); + } + return null; + } + + public static string GetFormat(short format) + { + lock (FormatList) + { + var pd = FormatList.FirstOrDefault(f => f.Format == format); + if (pd == null) + { + string name = QueryFormatName(format); + if (string.IsNullOrEmpty(name)) + name = string.Format("Unknown_Format_{0}", format); + pd = new ClipboardFormat(name, format); + FormatList.Add(pd); + } + return pd.Name; + } + } + + public static short GetFormat(string format) + { + lock (FormatList) + { + var pd = FormatList.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Name, format)); + if (pd == null) + { + int id = UnmanagedMethods.RegisterClipboardFormat(format); + if (id == 0) + throw new Win32Exception(); + pd = new ClipboardFormat(format, (short)id); + FormatList.Add(pd); + } + return pd.Format; + } + } + + + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f13dd3272c..dada2eb7e6 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; // ReSharper disable InconsistentNaming @@ -951,6 +952,28 @@ namespace Avalonia.Win32.Interop [DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)] public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target); + + [DllImport("ole32.dll", EntryPoint = "OleInitialize")] + public static extern HRESULT OleInitialize(IntPtr val); + + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); + + [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax); + + [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int RegisterClipboardFormat(string format); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GlobalSize(IntPtr hGlobal); + + [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)] + public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); + + public enum MONITOR { MONITOR_DEFAULTTONULL = 0x00000000, @@ -993,7 +1016,8 @@ namespace Avalonia.Win32.Interop public enum ClipboardFormat { CF_TEXT = 1, - CF_UNICODETEXT = 13 + CF_UNICODETEXT = 13, + CF_HDROP = 15 } public struct MSG @@ -1300,4 +1324,49 @@ namespace Avalonia.Win32.Interop uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); } + + [Flags] + internal enum DropEffect : int + { + None = 0, + Copy = 1, + Move = 2, + Link = 4, + Scroll = -2147483648, + } + + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("0000010E-0000-0000-C000-000000000046")] + [ComImport] + internal interface IOleDataObject + { + void GetData([In] ref FORMATETC format, out STGMEDIUM medium); + void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium); + [PreserveSig] + int QueryGetData([In] ref FORMATETC format); + [PreserveSig] + int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut); + void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release); + IEnumFORMATETC EnumFormatEtc(DATADIR direction); + [PreserveSig] + int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection); + void DUnadvise(int connection); + [PreserveSig] + int EnumDAdvise(out IEnumSTATDATA enumAdvise); + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000122-0000-0000-C000-000000000046")] + internal interface IDropTarget + { + [PreserveSig] + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + [PreserveSig] + UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + [PreserveSig] + UnmanagedMethods.HRESULT DragLeave(); + [PreserveSig] + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + } } diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs new file mode 100644 index 0000000000..0c597e3d17 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.Win32.Interop; + +namespace Zippr.UIServices.Avalonia.Windows +{ + class OleContext + { + private static OleContext fCurrent; + + internal static OleContext Current + { + get + { + if (!IsValidOleThread()) + return null; + + if (fCurrent == null) + fCurrent = new OleContext(); + return fCurrent; + } + } + + + private OleContext() + { + if (UnmanagedMethods.OleInitialize(IntPtr.Zero) != UnmanagedMethods.HRESULT.S_OK) + throw new SystemException("Failed to initialize OLE"); + } + + private static bool IsValidOleThread() + { + return Dispatcher.UIThread.CheckAccess() && + Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; + } + + internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target) + { + if (hwnd?.HandleDescriptor != "HWND" || target == null) + return false; + + return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs new file mode 100644 index 0000000000..6c86cd03e0 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using Avalonia.Controls.DragDrop; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class OleDataObject : IDragData + { + private IOleDataObject _wrapped; + + public OleDataObject(IOleDataObject wrapped) + { + _wrapped = wrapped; + } + + public bool Contains(string dataFormat) + { + return GetDataFormatsCore().Any(df => StringComparer.OrdinalIgnoreCase.Equals(df, dataFormat)); + } + + public IEnumerable GetDataFormats() + { + return GetDataFormatsCore().Distinct(); + } + + public string GetText() + { + return GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT) as string; + } + + public IEnumerable GetFileNames() + { + return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable; + } + + private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect) + { + FORMATETC formatEtc = new FORMATETC(); + formatEtc.cfFormat = ClipboardFormats.GetFormat(format); + formatEtc.dwAspect = aspect; + formatEtc.lindex = -1; + formatEtc.tymed = TYMED.TYMED_HGLOBAL; + if (_wrapped.QueryGetData(ref formatEtc) == 0) + { + _wrapped.GetData(ref formatEtc, out STGMEDIUM medium); + try + { + if (medium.unionmember != IntPtr.Zero && medium.tymed == TYMED.TYMED_HGLOBAL) + { + if (format == DataFormats.Text) + return ReadStringFromHGlobal(medium.unionmember); + if (format == DataFormats.FileNames) + return ReadFileNamesFromHGlobal(medium.unionmember); + return ReadBytesFromHGlobal(medium.unionmember); + } + } + finally + { + UnmanagedMethods.ReleaseStgMedium(ref medium); + } + } + return null; + } + + private static IEnumerable ReadFileNamesFromHGlobal(IntPtr hGlobal) + { + List files = new List(); + int fileCount = UnmanagedMethods.DragQueryFile(hGlobal, -1, null, 0); + if (fileCount > 0) + { + for (int i = 0; i < fileCount; i++) + { + int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0); + StringBuilder sb = new StringBuilder(pathLen+1); + + if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen) + { + files.Add(sb.ToString()); + } + } + } + return files; + } + + private static string ReadStringFromHGlobal(IntPtr hGlobal) + { + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + return Marshal.PtrToStringAuto(ptr); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private static byte[] ReadBytesFromHGlobal(IntPtr hGlobal) + { + IntPtr source = UnmanagedMethods.GlobalLock(hGlobal); + try + { + int size = (int)UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + byte[] data = new byte[size]; + Marshal.Copy(source, data, 0, size); + return data; + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private IEnumerable GetDataFormatsCore() + { + var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET); + if (enumFormat != null) + { + enumFormat.Reset(); + FORMATETC[] formats = new FORMATETC[1]; + int[] fetched = { 1 }; + while (fetched[0] > 0) + { + fetched[0] = 0; + if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0) + { + if (formats[0].ptd != IntPtr.Zero) + Marshal.FreeCoTaskMem(formats[0].ptd); + + yield return ClipboardFormats.GetFormat(formats[0].cfFormat); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs new file mode 100644 index 0000000000..b0f592403b --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -0,0 +1,131 @@ +using System; +using System.Runtime.InteropServices.ComTypes; +using Avalonia.Controls; +using Avalonia.Controls.DragDrop; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class OleDropTarget : IDropTarget + { + private readonly IDragDispatcher _dragDispatcher; + private readonly IInputElement _target; + + private IDragData _currentDrag = null; + + public OleDropTarget(IInputElement target) + { + _dragDispatcher = AvaloniaLocator.Current.GetService(); + _target = target; + } + + static DropEffect ConvertDropEffect(DragOperation operation) + { + DropEffect result = DropEffect.None; + if (operation.HasFlag(DragOperation.Copy)) + result |= DropEffect.Copy; + if (operation.HasFlag(DragOperation.Move)) + result |= DropEffect.Move; + if (operation.HasFlag(DragOperation.Link)) + result |= DropEffect.Link; + return result; + } + + static DragOperation ConvertDropEffect(DropEffect effect) + { + DragOperation result = DragOperation.None; + if (effect.HasFlag(DropEffect.Copy)) + result |= DragOperation.Copy; + if (effect.HasFlag(DropEffect.Move)) + result |= DragOperation.Move; + if (effect.HasFlag(DropEffect.Link)) + result |= DragOperation.Link; + return result; + } + + UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) + { + if (_dragDispatcher == null) + { + pdwEffect = DropEffect.None; + return UnmanagedMethods.HRESULT.S_OK; + } + + _currentDrag = new OleDataObject(pDataObj); + var dragLocation = GetDragLocation(pt); + + var operation = ConvertDropEffect(pdwEffect); + operation = _dragDispatcher.DragEnter(_target, dragLocation, _currentDrag, operation); + pdwEffect = ConvertDropEffect(operation); + + return UnmanagedMethods.HRESULT.S_OK; + } + + UnmanagedMethods.HRESULT IDropTarget.DragOver(int grfKeyState, long pt, ref DropEffect pdwEffect) + { + if (_dragDispatcher == null) + { + pdwEffect = DropEffect.None; + return UnmanagedMethods.HRESULT.S_OK; + } + + var dragLocation = GetDragLocation(pt); + + var operation = ConvertDropEffect(pdwEffect); + operation = _dragDispatcher.DragOver(_target, dragLocation, _currentDrag, operation); + pdwEffect = ConvertDropEffect(operation); + + return UnmanagedMethods.HRESULT.S_OK; + } + + UnmanagedMethods.HRESULT IDropTarget.DragLeave() + { + try + { + _dragDispatcher?.DragLeave(_target); + return UnmanagedMethods.HRESULT.S_OK; + } + finally + { + _currentDrag = null; + } + } + + UnmanagedMethods.HRESULT IDropTarget.Drop(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) + { + try + { + if (_dragDispatcher == null) + { + pdwEffect = DropEffect.None; + return UnmanagedMethods.HRESULT.S_OK; + } + + _currentDrag= new OleDataObject(pDataObj); + var dragLocation = GetDragLocation(pt); + + var operation = ConvertDropEffect(pdwEffect); + operation = _dragDispatcher.Drop(_target, dragLocation, _currentDrag, operation); + pdwEffect = ConvertDropEffect(operation); + + return UnmanagedMethods.HRESULT.S_OK; + } + finally + { + _currentDrag = null; + } + } + + private Point GetDragLocation(long dragPoint) + { + int x = (int)dragPoint; + int y = (int)(dragPoint >> 32); + + Point screenPt = new Point(x, y); + return _target.PointToClient(screenPt); + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3ba926b42a..2fca0baac7 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -14,6 +14,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; +using Zippr.UIServices.Avalonia.Windows; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 @@ -34,6 +35,7 @@ namespace Avalonia.Win32 private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; + private OleDropTarget _dropTarget; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -308,6 +310,7 @@ namespace Avalonia.Win32 public void SetInputRoot(IInputRoot inputRoot) { _owner = inputRoot; + CreateDropTarget(); } public void SetTitle(string title) @@ -689,6 +692,13 @@ namespace Avalonia.Win32 } } + private void CreateDropTarget() + { + OleDropTarget odt = new OleDropTarget(_owner); + if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false) + _dropTarget = odt; + } + private Point DipFromLParam(IntPtr lParam) { return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; From 50212da915cd32db81211a29840150d8eed6d1b2 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sat, 3 Mar 2018 16:20:12 +0100 Subject: [PATCH 05/32] DragDrop is now a static class --- src/Avalonia.Controls/DragDrop/DragDrop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DragDrop/DragDrop.cs b/src/Avalonia.Controls/DragDrop/DragDrop.cs index 3645409a5a..317a903d3c 100644 --- a/src/Avalonia.Controls/DragDrop/DragDrop.cs +++ b/src/Avalonia.Controls/DragDrop/DragDrop.cs @@ -2,7 +2,7 @@ namespace Avalonia.Controls.DragDrop { - public sealed class DragDrop : AvaloniaObject + public static class DragDrop { public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); From 4c362818779eec17a1c016faad5c22056055d160 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sat, 3 Mar 2018 16:29:41 +0100 Subject: [PATCH 06/32] renamed DragOperation to DragDropEffects --- .../DragDrop/DefaultDragDispatcher.cs | 22 +++++++++---------- .../{DragOperation.cs => DragDropEffects.cs} | 2 +- .../DragDrop/DragEventArgs.cs | 2 +- .../DragDrop/IDragDispatcher.cs | 6 ++--- src/OSX/Avalonia.MonoMac/DraggingInfo.cs | 18 +++++++-------- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 4 ++-- src/Windows/Avalonia.Win32/OleDropTarget.cs | 18 +++++++-------- 7 files changed, 36 insertions(+), 36 deletions(-) rename src/Avalonia.Controls/DragDrop/{DragOperation.cs => DragDropEffects.cs} (82%) diff --git a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs index 2ce5a32dc5..68fa6cad75 100644 --- a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs +++ b/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs @@ -23,37 +23,37 @@ namespace Avalonia.Controls.DragDrop return null; } - private DragOperation RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragOperation operation, IDragData data) + private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragDropEffects operation, IDragData data) { if (target == null) - return DragOperation.None; + return DragDropEffects.None; var args = new DragEventArgs(routedEvent, data) { RoutedEvent = routedEvent, - DragOperation = operation + DragEffects = operation }; target.RaiseEvent(args); - return args.DragOperation; + return args.DragEffects; } - public DragOperation DragEnter(IInputElement inputRoot, Point point, IDragData data, DragOperation operation) + public DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects) { _lastTarget = GetTarget(inputRoot, point); - return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, operation, data); + return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data); } - public DragOperation DragOver(IInputElement inputRoot, Point point, IDragData data, DragOperation operation) + public DragDropEffects DragOver(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects) { var target = GetTarget(inputRoot, point); if (target == _lastTarget) - return RaiseDragEvent(target, DragDrop.DragOverEvent, operation, data); + return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data); try { if (_lastTarget != null) _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); - return RaiseDragEvent(target, DragDrop.DragEnterEvent, operation, data); + return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data); } finally { @@ -75,11 +75,11 @@ namespace Avalonia.Controls.DragDrop } } - public DragOperation Drop(IInputElement inputRoot, Point point, IDragData data, DragOperation operation) + public DragDropEffects Drop(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects) { try { - return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, operation, data); + return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data); } finally { diff --git a/src/Avalonia.Controls/DragDrop/DragOperation.cs b/src/Avalonia.Controls/DragDrop/DragDropEffects.cs similarity index 82% rename from src/Avalonia.Controls/DragDrop/DragOperation.cs rename to src/Avalonia.Controls/DragDrop/DragDropEffects.cs index ffc6f666f7..cec3dab89d 100644 --- a/src/Avalonia.Controls/DragDrop/DragOperation.cs +++ b/src/Avalonia.Controls/DragDrop/DragDropEffects.cs @@ -3,7 +3,7 @@ namespace Avalonia.Controls.DragDrop { [Flags] - public enum DragOperation + public enum DragDropEffects { None = 0, Copy = 1, diff --git a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs b/src/Avalonia.Controls/DragDrop/DragEventArgs.cs index e8bd2528c7..2b6658d395 100644 --- a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs +++ b/src/Avalonia.Controls/DragDrop/DragEventArgs.cs @@ -4,7 +4,7 @@ namespace Avalonia.Controls.DragDrop { public class DragEventArgs : RoutedEventArgs { - public DragOperation DragOperation { get; set; } + public DragDropEffects DragEffects { get; set; } public IDragData Data { get; private set; } diff --git a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs index 05a211ff76..84c023ddbc 100644 --- a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs +++ b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs @@ -7,9 +7,9 @@ namespace Avalonia.Controls.DragDrop /// public interface IDragDispatcher { - DragOperation DragEnter(IInputElement inputRoot, Point point, IDragData data, DragOperation operation); - DragOperation DragOver(IInputElement inputRoot, Point point, IDragData data, DragOperation operation); + DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects); + DragDropEffects DragOver(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects); void DragLeave(IInputElement inputRoot); - DragOperation Drop(IInputElement inputRoot, Point point, IDragData data, DragOperation operation); + DragDropEffects Drop(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects); } } \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs index 8164470548..dccc39e861 100644 --- a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -17,27 +17,27 @@ namespace Avalonia.MonoMac } - internal static NSDragOperation ConvertDragOperation(DragOperation d) + internal static NSDragOperation ConvertDragOperation(DragDropEffects d) { NSDragOperation result = NSDragOperation.None; - if (d.HasFlag(DragOperation.Copy)) + if (d.HasFlag(DragDropEffects.Copy)) result |= NSDragOperation.Copy; - if (d.HasFlag(DragOperation.Link)) + if (d.HasFlag(DragDropEffects.Link)) result |= NSDragOperation.Link; - if (d.HasFlag(DragOperation.Move)) + if (d.HasFlag(DragDropEffects.Move)) result |= NSDragOperation.Move; return result; } - internal static DragOperation ConvertDragOperation(NSDragOperation d) + internal static DragDropEffects ConvertDragOperation(NSDragOperation d) { - DragOperation result = DragOperation.None; + DragDropEffects result = DragDropEffects.None; if (d.HasFlag(NSDragOperation.Copy)) - result |= DragOperation.Copy; + result |= DragDropEffects.Copy; if (d.HasFlag(NSDragOperation.Link)) - result |= DragOperation.Link; + result |= DragDropEffects.Link; if (d.HasFlag(NSDragOperation.Move)) - result |= DragOperation.Move; + result |= DragDropEffects.Move; return result; } diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 711f6d2787..4398b6bddb 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -201,7 +201,7 @@ namespace Avalonia.MonoMac dragOp = _dragDispatcher.DragOver(root, pt, info, dragOp); - return dragOp != DragOperation.None; + return dragOp != DragDropEffects.None; } public override bool PerformDragOperation(NSDraggingInfo sender) @@ -216,7 +216,7 @@ namespace Avalonia.MonoMac dragOp = _dragDispatcher.Drop(root, pt, info, dragOp); - return dragOp != DragOperation.None; + return dragOp != DragDropEffects.None; } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index b0f592403b..e3725d3bbf 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -22,27 +22,27 @@ namespace Avalonia.Win32 _target = target; } - static DropEffect ConvertDropEffect(DragOperation operation) + static DropEffect ConvertDropEffect(DragDropEffects operation) { DropEffect result = DropEffect.None; - if (operation.HasFlag(DragOperation.Copy)) + if (operation.HasFlag(DragDropEffects.Copy)) result |= DropEffect.Copy; - if (operation.HasFlag(DragOperation.Move)) + if (operation.HasFlag(DragDropEffects.Move)) result |= DropEffect.Move; - if (operation.HasFlag(DragOperation.Link)) + if (operation.HasFlag(DragDropEffects.Link)) result |= DropEffect.Link; return result; } - static DragOperation ConvertDropEffect(DropEffect effect) + static DragDropEffects ConvertDropEffect(DropEffect effect) { - DragOperation result = DragOperation.None; + DragDropEffects result = DragDropEffects.None; if (effect.HasFlag(DropEffect.Copy)) - result |= DragOperation.Copy; + result |= DragDropEffects.Copy; if (effect.HasFlag(DropEffect.Move)) - result |= DragOperation.Move; + result |= DragDropEffects.Move; if (effect.HasFlag(DropEffect.Link)) - result |= DragOperation.Link; + result |= DragDropEffects.Link; return result; } From 83b18944d3ac81ed089902fea6dd7105e3664c55 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sat, 3 Mar 2018 16:57:25 +0100 Subject: [PATCH 07/32] corrected namespace. (copy+paste error) --- src/Windows/Avalonia.Win32/OleContext.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs index 0c597e3d17..085c0f8ea9 100644 --- a/src/Windows/Avalonia.Win32/OleContext.cs +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -4,7 +4,7 @@ using Avalonia.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; -namespace Zippr.UIServices.Avalonia.Windows +namespace Avalonia.Win32 { class OleContext { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 2fca0baac7..ed224a6525 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -14,7 +14,6 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; -using Zippr.UIServices.Avalonia.Windows; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 From 0adc6e62de8a95129e4cc73b9772db838ec5b74e Mon Sep 17 00:00:00 2001 From: boombuler Date: Sat, 3 Mar 2018 17:36:11 +0100 Subject: [PATCH 08/32] renamed IDragData to IDataObject --- .../DragDrop/DefaultDragDispatcher.cs | 8 +++---- .../DragDrop/DragEventArgs.cs | 4 ++-- .../DragDrop/{IDragData.cs => IDataObject.cs} | 2 +- .../DragDrop/IDragDispatcher.cs | 6 ++--- src/OSX/Avalonia.MonoMac/DraggingInfo.cs | 2 +- .../Interop/UnmanagedMethods.cs | 24 +++---------------- src/Windows/Avalonia.Win32/OleDataObject.cs | 7 +++--- src/Windows/Avalonia.Win32/OleDropTarget.cs | 11 ++++----- 8 files changed, 22 insertions(+), 42 deletions(-) rename src/Avalonia.Controls/DragDrop/{IDragData.cs => IDataObject.cs} (88%) diff --git a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs index 68fa6cad75..2db227248b 100644 --- a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs +++ b/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls.DragDrop return null; } - private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragDropEffects operation, IDragData data) + private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data) { if (target == null) return DragDropEffects.None; @@ -36,13 +36,13 @@ namespace Avalonia.Controls.DragDrop return args.DragEffects; } - public DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects) + public DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { _lastTarget = GetTarget(inputRoot, point); return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data); } - public DragDropEffects DragOver(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects) + public DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { var target = GetTarget(inputRoot, point); @@ -75,7 +75,7 @@ namespace Avalonia.Controls.DragDrop } } - public DragDropEffects Drop(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects) + public DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { try { diff --git a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs b/src/Avalonia.Controls/DragDrop/DragEventArgs.cs index 2b6658d395..f31bfd4820 100644 --- a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs +++ b/src/Avalonia.Controls/DragDrop/DragEventArgs.cs @@ -6,9 +6,9 @@ namespace Avalonia.Controls.DragDrop { public DragDropEffects DragEffects { get; set; } - public IDragData Data { get; private set; } + public IDataObject Data { get; private set; } - public DragEventArgs(RoutedEvent routedEvent, IDragData data) + public DragEventArgs(RoutedEvent routedEvent, IDataObject data) : base(routedEvent) { this.Data = data; diff --git a/src/Avalonia.Controls/DragDrop/IDragData.cs b/src/Avalonia.Controls/DragDrop/IDataObject.cs similarity index 88% rename from src/Avalonia.Controls/DragDrop/IDragData.cs rename to src/Avalonia.Controls/DragDrop/IDataObject.cs index b6dc53d32d..5ffecc13ed 100644 --- a/src/Avalonia.Controls/DragDrop/IDragData.cs +++ b/src/Avalonia.Controls/DragDrop/IDataObject.cs @@ -2,7 +2,7 @@ namespace Avalonia.Controls.DragDrop { - public interface IDragData + public interface IDataObject { IEnumerable GetDataFormats(); diff --git a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs index 84c023ddbc..538ffa171b 100644 --- a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs +++ b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs @@ -7,9 +7,9 @@ namespace Avalonia.Controls.DragDrop /// public interface IDragDispatcher { - DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects); - DragDropEffects DragOver(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects); + DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects); + DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects); void DragLeave(IInputElement inputRoot); - DragDropEffects Drop(IInputElement inputRoot, Point point, IDragData data, DragDropEffects effects); + DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects); } } \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs index dccc39e861..ef4d9d99f6 100644 --- a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -7,7 +7,7 @@ using MonoMac.Foundation; namespace Avalonia.MonoMac { - class DraggingInfo : IDragData + class DraggingInfo : IDataObject { private readonly NSDraggingInfo _info; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index dada2eb7e6..f43cdb98cc 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1335,25 +1335,7 @@ namespace Avalonia.Win32.Interop Scroll = -2147483648, } - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("0000010E-0000-0000-C000-000000000046")] - [ComImport] - internal interface IOleDataObject - { - void GetData([In] ref FORMATETC format, out STGMEDIUM medium); - void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium); - [PreserveSig] - int QueryGetData([In] ref FORMATETC format); - [PreserveSig] - int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut); - void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release); - IEnumFORMATETC EnumFormatEtc(DATADIR direction); - [PreserveSig] - int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection); - void DUnadvise(int connection); - [PreserveSig] - int EnumDAdvise(out IEnumSTATDATA enumAdvise); - } + [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] @@ -1361,12 +1343,12 @@ namespace Avalonia.Win32.Interop internal interface IDropTarget { [PreserveSig] - UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragLeave(); [PreserveSig] - UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); } } diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 6c86cd03e0..bf6f3d98c3 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -6,14 +6,15 @@ using System.Runtime.InteropServices.ComTypes; using System.Text; using Avalonia.Controls.DragDrop; using Avalonia.Win32.Interop; +using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { - class OleDataObject : IDragData + class OleDataObject : Avalonia.Controls.DragDrop.IDataObject { - private IOleDataObject _wrapped; + private IDataObject _wrapped; - public OleDataObject(IOleDataObject wrapped) + public OleDataObject(IDataObject wrapped) { _wrapped = wrapped; } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index e3725d3bbf..925f9b7046 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -1,11 +1,8 @@ -using System; -using System.Runtime.InteropServices.ComTypes; -using Avalonia.Controls; -using Avalonia.Controls.DragDrop; +using Avalonia.Controls.DragDrop; using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.VisualTree; using Avalonia.Win32.Interop; +using IDataObject = Avalonia.Controls.DragDrop.IDataObject; +using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { @@ -14,7 +11,7 @@ namespace Avalonia.Win32 private readonly IDragDispatcher _dragDispatcher; private readonly IInputElement _target; - private IDragData _currentDrag = null; + private IDataObject _currentDrag = null; public OleDropTarget(IInputElement target) { From 4c6a341b73a5198d1539506c057c4e1b392e9f76 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sun, 4 Mar 2018 21:13:02 +0100 Subject: [PATCH 09/32] reworked Drag event dispatching --- src/Avalonia.Controls/Application.cs | 3 +- ...DefaultDragDispatcher.cs => DragDevice.cs} | 45 ++++++++--- .../DragDrop/IDragDispatcher.cs | 15 ---- .../DragDrop/Raw/IDragDevice.cs | 8 ++ .../DragDrop/Raw/RawDragEvent.cs | 31 ++++++++ .../DragDrop/Raw/RawDragEventType.cs | 10 +++ src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 65 +++++----------- src/Windows/Avalonia.Win32/OleDropTarget.cs | 76 +++++++++++++------ src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 9 files changed, 158 insertions(+), 97 deletions(-) rename src/Avalonia.Controls/DragDrop/{DefaultDragDispatcher.cs => DragDevice.cs} (62%) delete mode 100644 src/Avalonia.Controls/DragDrop/IDragDispatcher.cs create mode 100644 src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs create mode 100644 src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs create mode 100644 src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 63b17530ff..b751aeca60 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -13,6 +13,7 @@ using Avalonia.Styling; using Avalonia.Threading; using System.Reactive.Concurrency; using Avalonia.Controls.DragDrop; +using Avalonia.Controls.DragDrop.Raw; namespace Avalonia { @@ -236,7 +237,7 @@ namespace Avalonia .Bind().ToSingleton() .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) - .Bind().ToConstant(DefaultDragDispatcher.Instance); + .Bind().ToConstant(DragDevice.Instance); } } } diff --git a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/DragDevice.cs similarity index 62% rename from src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs rename to src/Avalonia.Controls/DragDrop/DragDevice.cs index 2db227248b..25f82ca3bc 100644 --- a/src/Avalonia.Controls/DragDrop/DefaultDragDispatcher.cs +++ b/src/Avalonia.Controls/DragDrop/DragDevice.cs @@ -2,19 +2,17 @@ using Avalonia.Interactivity; using Avalonia.VisualTree; using System.Linq; +using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Input.Raw; namespace Avalonia.Controls.DragDrop { - class DefaultDragDispatcher : IDragDispatcher + class DragDevice : IDragDevice { - public static readonly DefaultDragDispatcher Instance = new DefaultDragDispatcher(); - + public static readonly DragDevice Instance = new DragDevice(); + private Interactive _lastTarget = null; - private DefaultDragDispatcher() - { - } - private Interactive GetTarget(IInputElement root, Point local) { var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); @@ -36,13 +34,13 @@ namespace Avalonia.Controls.DragDrop return args.DragEffects; } - public DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) + private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { _lastTarget = GetTarget(inputRoot, point); return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data); } - public DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) + private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { var target = GetTarget(inputRoot, point); @@ -61,7 +59,7 @@ namespace Avalonia.Controls.DragDrop } } - public void DragLeave(IInputElement inputRoot) + private void DragLeave(IInputElement inputRoot) { if (_lastTarget == null) return; @@ -75,7 +73,7 @@ namespace Avalonia.Controls.DragDrop } } - public DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) + private DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { try { @@ -86,5 +84,30 @@ namespace Avalonia.Controls.DragDrop _lastTarget = null; } } + + public void ProcessRawEvent(RawInputEventArgs e) + { + if (!e.Handled && e is RawDragEvent margs) + ProcessRawEvent(margs); + } + + private void ProcessRawEvent(RawDragEvent e) + { + switch (e.Type) + { + case RawDragEventType.DragEnter: + e.Effects = DragEnter(e.InputRoot, e.Location, e.Data, e.Effects); + break; + case RawDragEventType.DragOver: + e.Effects = DragOver(e.InputRoot, e.Location, e.Data, e.Effects); + break; + case RawDragEventType.DragLeave: + DragLeave(e.InputRoot); + break; + case RawDragEventType.Drop: + e.Effects = Drop(e.InputRoot, e.Location, e.Data, e.Effects); + break; + } + } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs b/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs deleted file mode 100644 index 538ffa171b..0000000000 --- a/src/Avalonia.Controls/DragDrop/IDragDispatcher.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Input; - -namespace Avalonia.Controls.DragDrop -{ - /// - /// Dispatches Drag+Drop events to the correct visual targets, based on the input root and the drag location. - /// - public interface IDragDispatcher - { - DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects); - DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects); - void DragLeave(IInputElement inputRoot); - DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects); - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs b/src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs new file mode 100644 index 0000000000..d1c20fae55 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs @@ -0,0 +1,8 @@ +using Avalonia.Input; + +namespace Avalonia.Controls.DragDrop.Raw +{ + public interface IDragDevice : IInputDevice + { + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs b/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs new file mode 100644 index 0000000000..76a2c16b3b --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs @@ -0,0 +1,31 @@ +using System; +using Avalonia.Input; +using Avalonia.Input.Raw; + +namespace Avalonia.Controls.DragDrop.Raw +{ + public class RawDragEvent : RawInputEventArgs + { + public IInputElement InputRoot { get; } + public Point Location { get; } + public IDataObject Data { get; } + public DragDropEffects Effects { get; set; } + public RawDragEventType Type { get; } + + public RawDragEvent(IDragDevice inputDevice, RawDragEventType type, + IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects) + :base(inputDevice, GetTimeStamp()) + { + Type = type; + InputRoot = inputRoot; + Location = location; + Data = data; + Effects = effects; + } + + private static uint GetTimeStamp() + { + return (uint)0; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs b/src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs new file mode 100644 index 0000000000..44c4bbd595 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Controls.DragDrop.Raw +{ + public enum RawDragEventType + { + DragEnter, + DragOver, + DragLeave, + Drop + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 4398b6bddb..b767a86490 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Controls.DragDrop; +using Avalonia.Controls.DragDrop.Raw; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; @@ -19,6 +20,7 @@ namespace Avalonia.MonoMac { public TopLevelView View { get; } private readonly IMouseDevice _mouse = AvaloniaLocator.Current.GetService(); + private readonly IDragDevice _dragDevice = AvaloniaLocator.Current.GetService(); protected TopLevelImpl() { View = new TopLevelView(this); @@ -37,7 +39,6 @@ namespace Avalonia.MonoMac bool _isLeftPressed, _isRightPressed, _isMiddlePressed; private readonly IMouseDevice _mouse; private readonly IKeyboardDevice _keyboard; - private readonly IDragDispatcher _dragDispatcher; private NSTrackingArea _area; private NSCursor _cursor; private bool _nonUiRedrawQueued; @@ -54,7 +55,6 @@ namespace Avalonia.MonoMac _tl = tl; _mouse = AvaloniaLocator.Current.GetService(); _keyboard = AvaloniaLocator.Current.GetService(); - _dragDispatcher = AvaloniaLocator.Current.GetService(); RegisterForDraggedTypes(new string[] { "public.data" // register for any kind of data. @@ -151,72 +151,45 @@ namespace Avalonia.MonoMac UpdateCursor(); } - public override NSDragOperation DraggingEntered(NSDraggingInfo sender) + private NSDragOperation SendRawDragEvent(NSDraggingInfo sender, RawDragEventType type) { + Action input = _tl.Input; + IDragDevice dragDevice = _tl._dragDevice; IInputRoot root = _tl?.InputRoot; - if (root == null || _dragDispatcher == null) + if (root == null || dragDevice == null || input == null) return NSDragOperation.None; - + var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); DraggingInfo info = new DraggingInfo(sender); var pt = TranslateLocalPoint(info.Location); - - dragOp = _dragDispatcher.DragEnter(root, pt, info, dragOp); - - return DraggingInfo.ConvertDragOperation(dragOp); + var args = new RawDragEvent(dragDevice, type, root, pt, info, dragOp); + input(args); + return DraggingInfo.ConvertDragOperation(args.Effects); } - public override NSDragOperation DraggingUpdated(NSDraggingInfo sender) + public override NSDragOperation DraggingEntered(NSDraggingInfo sender) { - IInputRoot root = _tl?.InputRoot; - if (root == null || _dragDispatcher == null) - return NSDragOperation.None; + return SendRawDragEvent(sender, RawDragEventType.DragEnter); + } - var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); - DraggingInfo info = new DraggingInfo(sender); - var pt = TranslateLocalPoint(info.Location); - - dragOp = _dragDispatcher.DragOver(root, pt, info, dragOp); - - return DraggingInfo.ConvertDragOperation(dragOp); + public override NSDragOperation DraggingUpdated(NSDraggingInfo sender) + { + return SendRawDragEvent(sender, RawDragEventType.DragOver); } public override void DraggingExited(NSDraggingInfo sender) { - IInputRoot root = _tl?.InputRoot; - if (root == null || _dragDispatcher == null) - return; - _dragDispatcher.DragLeave(root); + SendRawDragEvent(sender, RawDragEventType.DragLeave); } public override bool PrepareForDragOperation(NSDraggingInfo sender) { - IInputRoot root = _tl?.InputRoot; - if (root == null || _dragDispatcher == null) - return false; - - var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); - DraggingInfo info = new DraggingInfo(sender); - var pt = TranslateLocalPoint(info.Location); - - dragOp = _dragDispatcher.DragOver(root, pt, info, dragOp); - - return dragOp != DragDropEffects.None; + return SendRawDragEvent(sender, RawDragEventType.DragOver) != NSDragOperation.None; } public override bool PerformDragOperation(NSDraggingInfo sender) { - IInputRoot root = _tl?.InputRoot; - if (root == null || _dragDispatcher == null) - return false; - - var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); - DraggingInfo info = new DraggingInfo(sender); - var pt = TranslateLocalPoint(info.Location); - - dragOp = _dragDispatcher.Drop(root, pt, info, dragOp); - - return dragOp != DragDropEffects.None; + return SendRawDragEvent(sender, RawDragEventType.Drop) != NSDragOperation.None; } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 925f9b7046..c16c5058f3 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -1,5 +1,7 @@ using Avalonia.Controls.DragDrop; +using Avalonia.Controls.DragDrop.Raw; using Avalonia.Input; +using Avalonia.Platform; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Controls.DragDrop.IDataObject; using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; @@ -8,14 +10,16 @@ namespace Avalonia.Win32 { class OleDropTarget : IDropTarget { - private readonly IDragDispatcher _dragDispatcher; private readonly IInputElement _target; + private readonly ITopLevelImpl _tl; + private readonly IDragDevice _dragDevice; private IDataObject _currentDrag = null; - public OleDropTarget(IInputElement target) + public OleDropTarget(ITopLevelImpl tl, IInputElement target) { - _dragDispatcher = AvaloniaLocator.Current.GetService(); + _dragDevice = AvaloniaLocator.Current.GetService(); + _tl = tl; _target = target; } @@ -42,38 +46,50 @@ namespace Avalonia.Win32 result |= DragDropEffects.Link; return result; } - + UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) { - if (_dragDispatcher == null) + var dispatch = _tl?.Input; + if (dispatch == null) { pdwEffect = DropEffect.None; return UnmanagedMethods.HRESULT.S_OK; } _currentDrag = new OleDataObject(pDataObj); - var dragLocation = GetDragLocation(pt); - - var operation = ConvertDropEffect(pdwEffect); - operation = _dragDispatcher.DragEnter(_target, dragLocation, _currentDrag, operation); - pdwEffect = ConvertDropEffect(operation); + var args = new RawDragEvent( + _dragDevice, + RawDragEventType.DragEnter, + _target, + GetDragLocation(pt), + _currentDrag, + ConvertDropEffect(pdwEffect) + ); + dispatch(args); + pdwEffect = ConvertDropEffect(args.Effects); return UnmanagedMethods.HRESULT.S_OK; } UnmanagedMethods.HRESULT IDropTarget.DragOver(int grfKeyState, long pt, ref DropEffect pdwEffect) { - if (_dragDispatcher == null) + var dispatch = _tl?.Input; + if (dispatch == null) { pdwEffect = DropEffect.None; return UnmanagedMethods.HRESULT.S_OK; } - var dragLocation = GetDragLocation(pt); - - var operation = ConvertDropEffect(pdwEffect); - operation = _dragDispatcher.DragOver(_target, dragLocation, _currentDrag, operation); - pdwEffect = ConvertDropEffect(operation); + var args = new RawDragEvent( + _dragDevice, + RawDragEventType.DragOver, + _target, + GetDragLocation(pt), + _currentDrag, + ConvertDropEffect(pdwEffect) + ); + dispatch(args); + pdwEffect = ConvertDropEffect(args.Effects); return UnmanagedMethods.HRESULT.S_OK; } @@ -82,7 +98,14 @@ namespace Avalonia.Win32 { try { - _dragDispatcher?.DragLeave(_target); + _tl?.Input(new RawDragEvent( + _dragDevice, + RawDragEventType.DragLeave, + _target, + default(Point), + null, + DragDropEffects.None + )); return UnmanagedMethods.HRESULT.S_OK; } finally @@ -95,18 +118,25 @@ namespace Avalonia.Win32 { try { - if (_dragDispatcher == null) + var dispatch = _tl?.Input; + if (dispatch == null) { pdwEffect = DropEffect.None; return UnmanagedMethods.HRESULT.S_OK; } _currentDrag= new OleDataObject(pDataObj); - var dragLocation = GetDragLocation(pt); - - var operation = ConvertDropEffect(pdwEffect); - operation = _dragDispatcher.Drop(_target, dragLocation, _currentDrag, operation); - pdwEffect = ConvertDropEffect(operation); + + var args = new RawDragEvent( + _dragDevice, + RawDragEventType.Drop, + _target, + GetDragLocation(pt), + _currentDrag, + ConvertDropEffect(pdwEffect) + ); + dispatch(args); + pdwEffect = ConvertDropEffect(args.Effects); return UnmanagedMethods.HRESULT.S_OK; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ed224a6525..85e5b5b4b9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -693,7 +693,7 @@ namespace Avalonia.Win32 private void CreateDropTarget() { - OleDropTarget odt = new OleDropTarget(_owner); + OleDropTarget odt = new OleDropTarget(this, _owner); if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false) _dropTarget = odt; } From be68b32440a4d96eb6268abee1c390dbca6863a8 Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 07:25:46 +0100 Subject: [PATCH 10/32] reworked the dataobject for drag+drop --- src/Avalonia.Controls/DragDrop/IDataObject.cs | 2 + src/OSX/Avalonia.MonoMac/DraggingInfo.cs | 13 +- .../Avalonia.Win32/ClipboardFormats.cs | 20 +- src/Windows/Avalonia.Win32/DataObject.cs | 312 ++++++++++++++++++ .../Interop/UnmanagedMethods.cs | 53 ++- src/Windows/Avalonia.Win32/OleDataObject.cs | 5 + src/Windows/Avalonia.Win32/OleDragSource.cs | 41 +++ src/Windows/Avalonia.Win32/OleDropTarget.cs | 11 +- 8 files changed, 437 insertions(+), 20 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/DataObject.cs create mode 100644 src/Windows/Avalonia.Win32/OleDragSource.cs diff --git a/src/Avalonia.Controls/DragDrop/IDataObject.cs b/src/Avalonia.Controls/DragDrop/IDataObject.cs index 5ffecc13ed..c8fd720c48 100644 --- a/src/Avalonia.Controls/DragDrop/IDataObject.cs +++ b/src/Avalonia.Controls/DragDrop/IDataObject.cs @@ -11,5 +11,7 @@ namespace Avalonia.Controls.DragDrop string GetText(); IEnumerable GetFileNames(); + + object Get(string dataFormat); } } \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs index ef4d9d99f6..2fb28a50f5 100644 --- a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -15,8 +15,7 @@ namespace Avalonia.MonoMac { _info = info; } - - + internal static NSDragOperation ConvertDragOperation(DragDropEffects d) { NSDragOperation result = NSDragOperation.None; @@ -77,5 +76,15 @@ namespace Avalonia.MonoMac { return GetDataFormats().Any(f => f == dataFormat); } + + public object Get(string dataFormat) + { + if (dataFormat == DataFormats.Text) + return GetText(); + if (dataFormat == DataFormats.FileNames) + return GetFileNames(); + + return _info.DraggingPasteboard.GetDataForType(dataFormat).ToArray(); + } } } \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index 254facf81a..a59c6a1664 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -10,34 +10,34 @@ namespace Avalonia.Win32 { static class ClipboardFormats { + private const int MAX_FORMAT_NAME_LENGTH = 260; + class ClipboardFormat { public short Format { get; private set; } public string Name { get; private set; } + public short[] Synthesized { get; private set; } - public ClipboardFormat(string name, short format) + public ClipboardFormat(string name, short format, params short[] synthesized) { Format = format; Name = name; + Synthesized = synthesized; } } private static readonly List FormatList = new List() { - new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT), + new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (short)UnmanagedMethods.ClipboardFormat.CF_TEXT), new ClipboardFormat(DataFormats.FileNames, (short)UnmanagedMethods.ClipboardFormat.CF_HDROP), }; private static string QueryFormatName(short format) { - int len = UnmanagedMethods.GetClipboardFormatName(format, null, 0); - if (len > 0) - { - StringBuilder sb = new StringBuilder(len); - if (UnmanagedMethods.GetClipboardFormatName(format, sb, len) <= len) - return sb.ToString(); - } + StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH); + if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0) + return sb.ToString(); return null; } @@ -45,7 +45,7 @@ namespace Avalonia.Win32 { lock (FormatList) { - var pd = FormatList.FirstOrDefault(f => f.Format == format); + var pd = FormatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0); if (pd == null) { string name = QueryFormatName(format); diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs new file mode 100644 index 0000000000..6248afd7bb --- /dev/null +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -0,0 +1,312 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using Avalonia.Controls.DragDrop; +using Avalonia.Win32.Interop; +using IDataObject = Avalonia.Controls.DragDrop.IDataObject; +using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; + +namespace Avalonia.Win32 +{ + class DataObject : IDataObject, IOleDataObject + { + class FormatEnumerator : IEnumFORMATETC + { + private FORMATETC[] _formats; + private int _current; + + private FormatEnumerator(FORMATETC[] formats, int current) + { + _formats = formats; + _current = current; + } + + public FormatEnumerator(IDataObject dataobj) + { + _formats = dataobj.GetDataFormats().Select(ConvertToFormatEtc).ToArray(); + _current = 0; + } + + private FORMATETC ConvertToFormatEtc(string aFormatName) + { + FORMATETC result = default(FORMATETC); + result.cfFormat = ClipboardFormats.GetFormat(aFormatName); + result.dwAspect = DVASPECT.DVASPECT_CONTENT; + result.ptd = IntPtr.Zero; + result.lindex = -1; + result.tymed = TYMED.TYMED_HGLOBAL; + return result; + } + + public void Clone(out IEnumFORMATETC newEnum) + { + newEnum = new FormatEnumerator(_formats, _current); + } + + public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched) + { + if (rgelt == null) + return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG); + + int i = 0; + while (i < celt && _current < _formats.Length) + { + rgelt[i] = _formats[_current]; + _current++; + i++; + } + if (pceltFetched != null) + pceltFetched[0] = i; + + if (i != celt) + return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + + public int Reset() + { + _current = 0; + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + + public int Skip(int celt) + { + _current += Math.Min(celt, int.MaxValue - _current); + if (_current >= _formats.Length) + return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + } + + private const int DV_E_TYMED = unchecked((int)0x80040069); + private const int DV_E_DVASPECT = unchecked((int)0x8004006B); + private const int DV_E_FORMATETC = unchecked((int)0x80040064); + private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003); + private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070); + private const int GMEM_ZEROINIT = 0x0040; + private const int GMEM_MOVEABLE = 0x0002; + + + IDataObject _wrapped; + + public DataObject(IDataObject wrapped) + { + _wrapped = wrapped; + } + + #region IDataObject + bool IDataObject.Contains(string dataFormat) + { + return _wrapped.Contains(dataFormat); + } + + IEnumerable IDataObject.GetDataFormats() + { + return _wrapped.GetDataFormats(); + } + + IEnumerable IDataObject.GetFileNames() + { + return _wrapped.GetFileNames(); + } + + string IDataObject.GetText() + { + return _wrapped.GetText(); + } + + object IDataObject.Get(string dataFormat) + { + return _wrapped.Get(dataFormat); + } + #endregion + + #region IOleDataObject + + int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) + { + if (_wrapped is IOleDataObject ole) + return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection); + connection = 0; + return OLE_E_ADVISENOTSUPPORTED; + } + + void IOleDataObject.DUnadvise(int connection) + { + if (_wrapped is IOleDataObject ole) + ole.DUnadvise(connection); + Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED); + } + + int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise) + { + if (_wrapped is IOleDataObject ole) + return ole.EnumDAdvise(out enumAdvise); + + enumAdvise = null; + return OLE_E_ADVISENOTSUPPORTED; + } + + IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction) + { + if (_wrapped is IOleDataObject ole) + return ole.EnumFormatEtc(direction); + if (direction == DATADIR.DATADIR_GET) + return new FormatEnumerator(_wrapped); + throw new NotSupportedException(); + } + + int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) + { + if (_wrapped is IOleDataObject ole) + return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut); + + formatOut = new FORMATETC(); + formatOut.ptd = IntPtr.Zero; + return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL); + } + + void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium) + { + if (_wrapped is IOleDataObject ole) + { + ole.GetData(ref format, out medium); + return; + } + if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) + Marshal.ThrowExceptionForHR(DV_E_TYMED); + + if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) + Marshal.ThrowExceptionForHR(DV_E_DVASPECT); + + string fmt = ClipboardFormats.GetFormat(format.cfFormat); + if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) + Marshal.ThrowExceptionForHR(DV_E_FORMATETC); + + medium = default(STGMEDIUM); + medium.tymed = TYMED.TYMED_HGLOBAL; + int result = WriteDataToHGlobal(fmt, ref medium.unionmember); + Marshal.ThrowExceptionForHR(result); + } + + void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) + { + if (_wrapped is IOleDataObject ole) + { + ole.GetDataHere(ref format, ref medium); + return; + } + + if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) + Marshal.ThrowExceptionForHR(DV_E_TYMED); + + if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) + Marshal.ThrowExceptionForHR(DV_E_DVASPECT); + + string fmt = ClipboardFormats.GetFormat(format.cfFormat); + if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) + Marshal.ThrowExceptionForHR(DV_E_FORMATETC); + + if (medium.unionmember == IntPtr.Zero) + Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL); + + int result = WriteDataToHGlobal(fmt, ref medium.unionmember); + Marshal.ThrowExceptionForHR(result); + } + + int IOleDataObject.QueryGetData(ref FORMATETC format) + { + if (_wrapped is IOleDataObject ole) + return ole.QueryGetData(ref format); + if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) + return DV_E_DVASPECT; + if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) + return DV_E_TYMED; + + string dataFormat = ClipboardFormats.GetFormat(format.cfFormat); + if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat)) + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + return DV_E_FORMATETC; + } + + void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) + { + if (_wrapped is IOleDataObject ole) + { + ole.SetData(ref formatIn, ref medium, release); + return; + } + Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL)); + } + + private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal) + { + object data = _wrapped.Get(dataFormat); + if (dataFormat == DataFormats.Text || data is string) + return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data)); + if (dataFormat == DataFormats.FileNames && data is IEnumerable files) + return WriteFileListToHGlobal(ref hGlobal, files); + return DV_E_TYMED; + } + + private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable files) + { + if (!files?.Any() ?? false) + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + + char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray(); + _DROPFILES df = new _DROPFILES(); + df.pFiles = Marshal.SizeOf<_DROPFILES>(); + df.fWide = true; + + int required = (filesStr.Length * sizeof(char)) + Marshal.SizeOf<_DROPFILES>(); + if (hGlobal == IntPtr.Zero) + hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required); + + long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + if (required > available) + return STG_E_MEDIUMFULL; + + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + Marshal.StructureToPtr(df, ptr, false); + + Marshal.Copy(filesStr, 0, ptr + Marshal.SizeOf<_DROPFILES>(), filesStr.Length); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private int WriteStringToHGlobal(ref IntPtr hGlobal, string data) + { + int required = (data.Length + 1) * sizeof(char); + if (hGlobal == IntPtr.Zero) + hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, required); + + long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + if (required > available) + return STG_E_MEDIUMFULL; + + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + char[] chars = (data + '\0').ToCharArray(); + Marshal.Copy(chars, 0, ptr, chars.Length); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + #endregion + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f43cdb98cc..aa86ab0f8d 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -973,7 +973,11 @@ namespace Avalonia.Win32.Interop [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)] public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); - + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] + public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); + + + public enum MONITOR { MONITOR_DEFAULTTONULL = 0x00000000, @@ -1013,11 +1017,28 @@ namespace Avalonia.Win32.Interop MDT_DEFAULT = MDT_EFFECTIVE_DPI } - public enum ClipboardFormat + public enum ClipboardFormat { + /// + /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text. + /// CF_TEXT = 1, + /// + /// A handle to a bitmap + /// + CF_BITMAP = 2, + /// + /// A memory object containing a BITMAPINFO structure followed by the bitmap bits. + /// + CF_DIB = 3, + /// + /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. + /// CF_UNICODETEXT = 13, - CF_HDROP = 15 + /// + /// A handle to type HDROP that identifies a list of files. + /// + CF_HDROP = 15, } public struct MSG @@ -1160,7 +1181,9 @@ namespace Avalonia.Win32.Interop S_FALSE = 0x0001, S_OK = 0x0000, E_INVALIDARG = 0x80070057, - E_OUTOFMEMORY = 0x8007000E + E_OUTOFMEMORY = 0x8007000E, + E_NOTIMPL = 0x80004001, + E_UNEXPECTED = 0x8000FFFF, } public enum Icons @@ -1351,4 +1374,26 @@ namespace Avalonia.Win32.Interop [PreserveSig] UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000121-0000-0000-C000-000000000046")] + internal interface IDropSource + { + [PreserveSig] + int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState); + [PreserveSig] + int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect); + } + + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct _DROPFILES + { + public Int32 pFiles; + public Int32 X; + public Int32 Y; + public bool fNC; + public bool fWide; + } } diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index bf6f3d98c3..fe6faa162c 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -39,6 +39,11 @@ namespace Avalonia.Win32 return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable; } + public object Get(string dataFormat) + { + return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT); + } + private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect) { FORMATETC formatEtc = new FORMATETC(); diff --git a/src/Windows/Avalonia.Win32/OleDragSource.cs b/src/Windows/Avalonia.Win32/OleDragSource.cs new file mode 100644 index 0000000000..1bff984087 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleDragSource.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class OleDragSource : IDropSource + { + private const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102; + private const int DRAGDROP_S_DROP = 0x00040100; + private const int DRAGDROP_S_CANCEL = 0x00040101; + + private const int KEYSTATE_LEFTMB = 1; + private const int KEYSTATE_MIDDLEMB = 16; + private const int KEYSTATE_RIGHTMB = 2; + private static readonly int[] MOUSE_BUTTONS = new int[] { KEYSTATE_LEFTMB, KEYSTATE_MIDDLEMB, KEYSTATE_RIGHTMB }; + + public int QueryContinueDrag(int fEscapePressed, int grfKeyState) + { + if (fEscapePressed != 0) + return DRAGDROP_S_CANCEL; + + int pressedMouseButtons = MOUSE_BUTTONS.Where(mb => (grfKeyState & mb) == mb).Count(); + + if (pressedMouseButtons >= 2) + return DRAGDROP_S_CANCEL; + if (pressedMouseButtons == 0) + return DRAGDROP_S_DROP; + + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + + public int GiveFeedback(int dwEffect) + { + if (dwEffect != 0) + return DRAGDROP_S_USEDEFAULTCURSORS; + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + } +} diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index c16c5058f3..6bc298951e 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -55,8 +55,9 @@ namespace Avalonia.Win32 pdwEffect = DropEffect.None; return UnmanagedMethods.HRESULT.S_OK; } - - _currentDrag = new OleDataObject(pDataObj); + _currentDrag = pDataObj as IDataObject; + if (_currentDrag == null) + _currentDrag = new OleDataObject(pDataObj); var args = new RawDragEvent( _dragDevice, RawDragEventType.DragEnter, @@ -124,8 +125,10 @@ namespace Avalonia.Win32 pdwEffect = DropEffect.None; return UnmanagedMethods.HRESULT.S_OK; } - - _currentDrag= new OleDataObject(pDataObj); + + _currentDrag = pDataObj as IDataObject; + if (_currentDrag == null) + _currentDrag= new OleDataObject(pDataObj); var args = new RawDragEvent( _dragDevice, From f57fbdc2da30ff6ab7b713e5fae2c7cb867b79bc Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 07:31:54 +0100 Subject: [PATCH 11/32] renamed DragDevice to DragDropDevice --- .../DragDrop/{DragDevice.cs => DragDropDevice.cs} | 4 ++-- .../DragDrop/Raw/{IDragDevice.cs => IDragDropDevice.cs} | 2 +- src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs | 2 +- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 4 ++-- src/Windows/Avalonia.Win32/OleDropTarget.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) rename src/Avalonia.Controls/DragDrop/{DragDevice.cs => DragDropDevice.cs} (96%) rename src/Avalonia.Controls/DragDrop/Raw/{IDragDevice.cs => IDragDropDevice.cs} (60%) diff --git a/src/Avalonia.Controls/DragDrop/DragDevice.cs b/src/Avalonia.Controls/DragDrop/DragDropDevice.cs similarity index 96% rename from src/Avalonia.Controls/DragDrop/DragDevice.cs rename to src/Avalonia.Controls/DragDrop/DragDropDevice.cs index 25f82ca3bc..0aadda79f4 100644 --- a/src/Avalonia.Controls/DragDrop/DragDevice.cs +++ b/src/Avalonia.Controls/DragDrop/DragDropDevice.cs @@ -7,9 +7,9 @@ using Avalonia.Input.Raw; namespace Avalonia.Controls.DragDrop { - class DragDevice : IDragDevice + class DragDropDevice : IDragDropDevice { - public static readonly DragDevice Instance = new DragDevice(); + public static readonly DragDropDevice Instance = new DragDropDevice(); private Interactive _lastTarget = null; diff --git a/src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs b/src/Avalonia.Controls/DragDrop/Raw/IDragDropDevice.cs similarity index 60% rename from src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs rename to src/Avalonia.Controls/DragDrop/Raw/IDragDropDevice.cs index d1c20fae55..6e2db67ac3 100644 --- a/src/Avalonia.Controls/DragDrop/Raw/IDragDevice.cs +++ b/src/Avalonia.Controls/DragDrop/Raw/IDragDropDevice.cs @@ -2,7 +2,7 @@ namespace Avalonia.Controls.DragDrop.Raw { - public interface IDragDevice : IInputDevice + public interface IDragDropDevice : IInputDevice { } } \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs b/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs index 76a2c16b3b..e9c1119e44 100644 --- a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs +++ b/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.DragDrop.Raw public DragDropEffects Effects { get; set; } public RawDragEventType Type { get; } - public RawDragEvent(IDragDevice inputDevice, RawDragEventType type, + public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type, IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects) :base(inputDevice, GetTimeStamp()) { diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index b767a86490..20d255a608 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -20,7 +20,7 @@ namespace Avalonia.MonoMac { public TopLevelView View { get; } private readonly IMouseDevice _mouse = AvaloniaLocator.Current.GetService(); - private readonly IDragDevice _dragDevice = AvaloniaLocator.Current.GetService(); + private readonly IDragDropDevice _dragDevice = AvaloniaLocator.Current.GetService(); protected TopLevelImpl() { View = new TopLevelView(this); @@ -154,7 +154,7 @@ namespace Avalonia.MonoMac private NSDragOperation SendRawDragEvent(NSDraggingInfo sender, RawDragEventType type) { Action input = _tl.Input; - IDragDevice dragDevice = _tl._dragDevice; + IDragDropDevice dragDevice = _tl._dragDevice; IInputRoot root = _tl?.InputRoot; if (root == null || dragDevice == null || input == null) return NSDragOperation.None; diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 6bc298951e..31db5f88a9 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -12,13 +12,13 @@ namespace Avalonia.Win32 { private readonly IInputElement _target; private readonly ITopLevelImpl _tl; - private readonly IDragDevice _dragDevice; + private readonly IDragDropDevice _dragDevice; private IDataObject _currentDrag = null; public OleDropTarget(ITopLevelImpl tl, IInputElement target) { - _dragDevice = AvaloniaLocator.Current.GetService(); + _dragDevice = AvaloniaLocator.Current.GetService(); _tl = tl; _target = target; } From 30b5a1fd4df156180b2151562d5689f9a3ab4185 Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 07:43:51 +0100 Subject: [PATCH 12/32] fixed locator setup --- src/Avalonia.Controls/Application.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index b751aeca60..0a9bf4eab2 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -237,7 +237,7 @@ namespace Avalonia .Bind().ToSingleton() .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) - .Bind().ToConstant(DragDevice.Instance); + .Bind().ToConstant(DragDropDevice.Instance); } } } From c1abc0535534e66697b08b0cd3e58530189ef297 Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 07:45:55 +0100 Subject: [PATCH 13/32] Modified the ControlCatalog examples to run in STA ApartmentState --- samples/ControlCatalog.Desktop/Program.cs | 1 + samples/ControlCatalog.NetCore/Program.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b151cabf43..a2048005a4 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -10,6 +10,7 @@ namespace ControlCatalog { internal class Program { + [STAThread] static void Main(string[] args) { // TODO: Make this work with GTK/Skia/Cairo depending on command-line args diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 346535d39d..b45a93455e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -9,8 +9,10 @@ namespace ControlCatalog.NetCore { static class Program { + static void Main(string[] args) { + Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); if (args.Contains("--wait-for-attach")) { Console.WriteLine("Attach debugger and use 'Set next statement'"); From 411e3c8860b1756ce8b01943a1091d5481493b02 Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 07:46:28 +0100 Subject: [PATCH 14/32] started dragging API --- src/Avalonia.Controls/DragDrop/DataObject.cs | 43 ++++++++++++++++++++ src/Avalonia.Controls/DragDrop/DragDrop.cs | 10 +++++ 2 files changed, 53 insertions(+) create mode 100644 src/Avalonia.Controls/DragDrop/DataObject.cs diff --git a/src/Avalonia.Controls/DragDrop/DataObject.cs b/src/Avalonia.Controls/DragDrop/DataObject.cs new file mode 100644 index 0000000000..e0e7b6d069 --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DataObject.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Controls.DragDrop +{ + public class DataObject : IDataObject + { + private readonly Dictionary _items = new Dictionary(); + + public bool Contains(string dataFormat) + { + return _items.ContainsKey(dataFormat); + } + + public object Get(string dataFormat) + { + if (_items.ContainsKey(dataFormat)) + return _items[dataFormat]; + return null; + } + + public IEnumerable GetDataFormats() + { + return _items.Keys; + } + + public IEnumerable GetFileNames() + { + return Get(DataFormats.FileNames) as IEnumerable; + } + + public string GetText() + { + return Get(DataFormats.Text) as string; + } + + public void Set(string dataFormat, object value) + { + _items[dataFormat] = value; + } + } +} diff --git a/src/Avalonia.Controls/DragDrop/DragDrop.cs b/src/Avalonia.Controls/DragDrop/DragDrop.cs index 317a903d3c..af003516b1 100644 --- a/src/Avalonia.Controls/DragDrop/DragDrop.cs +++ b/src/Avalonia.Controls/DragDrop/DragDrop.cs @@ -20,5 +20,15 @@ namespace Avalonia.Controls.DragDrop { interactive.SetValue(AcceptDragProperty, value); } + + /// + /// Starts a dragging operation with the given and returns the applied drop effect from the target. + /// + /// + public static DragDropEffects DoDragDrop(this Interactive source, IDataObject data, DragDropEffects allowedEffects) + { + + return DragDropEffects.None; + } } } \ No newline at end of file From 7b3942685e4495231a658afb22d204508d995a9d Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 14:02:06 +0100 Subject: [PATCH 15/32] Basic generic + Windows Drag support. --- src/Avalonia.Controls/Application.cs | 4 +- src/Avalonia.Controls/DragDrop/DragDrop.cs | 10 +- src/Avalonia.Controls/DragDrop/DragSource.cs | 129 ++++++++++++++++++ .../Platform/IPlatformDragSource.cs | 14 ++ src/Windows/Avalonia.Win32/DragSource.cs | 28 ++++ src/Windows/Avalonia.Win32/OleDragSource.cs | 4 +- src/Windows/Avalonia.Win32/OleDropTarget.cs | 4 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 3 +- 8 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 src/Avalonia.Controls/DragDrop/DragSource.cs create mode 100644 src/Avalonia.Controls/Platform/IPlatformDragSource.cs create mode 100644 src/Windows/Avalonia.Win32/DragSource.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 0a9bf4eab2..6ed797dc51 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -14,6 +14,7 @@ using Avalonia.Threading; using System.Reactive.Concurrency; using Avalonia.Controls.DragDrop; using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Controls.Platform; namespace Avalonia { @@ -237,7 +238,8 @@ namespace Avalonia .Bind().ToSingleton() .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) - .Bind().ToConstant(DragDropDevice.Instance); + .Bind().ToConstant(DragDropDevice.Instance) + .Bind().ToTransient(); } } } diff --git a/src/Avalonia.Controls/DragDrop/DragDrop.cs b/src/Avalonia.Controls/DragDrop/DragDrop.cs index af003516b1..bbac80e38f 100644 --- a/src/Avalonia.Controls/DragDrop/DragDrop.cs +++ b/src/Avalonia.Controls/DragDrop/DragDrop.cs @@ -1,4 +1,6 @@ -using Avalonia.Interactivity; +using System.Threading.Tasks; +using Avalonia.Controls.Platform; +using Avalonia.Interactivity; namespace Avalonia.Controls.DragDrop { @@ -25,10 +27,10 @@ namespace Avalonia.Controls.DragDrop /// Starts a dragging operation with the given and returns the applied drop effect from the target. /// /// - public static DragDropEffects DoDragDrop(this Interactive source, IDataObject data, DragDropEffects allowedEffects) + public static Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) { - - return DragDropEffects.None; + var src = AvaloniaLocator.Current.GetService(); + return src?.DoDragDrop(data, allowedEffects) ?? Task.FromResult(DragDropEffects.None); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/DragDrop/DragSource.cs b/src/Avalonia.Controls/DragDrop/DragSource.cs new file mode 100644 index 0000000000..f899eabfbd --- /dev/null +++ b/src/Avalonia.Controls/DragDrop/DragSource.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.DragDrop +{ + class DragSource : IPlatformDragSource + { + private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton; + private readonly IDragDropDevice _dragDrop; + private readonly IInputManager _inputManager; + + + private readonly Subject _result = new Subject(); + private IDataObject _draggedData; + private IInputRoot _lastRoot; + private InputModifiers? _initialInputModifiers; + + public DragSource() + { + _inputManager = AvaloniaLocator.Current.GetService(); + _dragDrop = AvaloniaLocator.Current.GetService(); + } + + public async Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + Dispatcher.UIThread.VerifyAccess(); + if (_draggedData == null) + { + _draggedData = data; + _lastRoot = null; + + using (_inputManager.PreProcess.OfType().Subscribe(e => ProcessMouseEvents(e, allowedEffects))) + { + var effect = await _result.FirstAsync(); + return effect; + } + } + return DragDropEffects.None; + } + + private DragDropEffects RaiseDragEvent(RawDragEventType type, IInputElement root, Point pt, DragDropEffects allowedEffects) + { + RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, allowedEffects); + var tl = root.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + tl.PlatformImpl.Input(rawEvent); + return rawEvent.Effects; + } + + private void ProcessMouseEvents(RawMouseEventArgs e, DragDropEffects allowedEffects) + { + if (!_initialInputModifiers.HasValue) + _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS; + + void CancelDragging() + { + if (_lastRoot != null) + RaiseDragEvent(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), allowedEffects); + _result.OnNext(DragDropEffects.None); + e.Handled = true; + } + void AcceptDragging() + { + var result = RaiseDragEvent(RawDragEventType.Drop, e.Root, e.Position, allowedEffects) & allowedEffects; + _result.OnNext(result); + e.Handled = true; + } + + switch (e.Type) + { + case RawMouseEventType.LeftButtonDown: + case RawMouseEventType.RightButtonDown: + case RawMouseEventType.MiddleButtonDown: + case RawMouseEventType.NonClientLeftButtonDown: + CancelDragging(); + return; + case RawMouseEventType.LeaveWindow: + RaiseDragEvent(RawDragEventType.DragLeave, e.Root, e.Position, allowedEffects); + break; + case RawMouseEventType.LeftButtonUp: + if (_initialInputModifiers.Value.HasFlag(InputModifiers.LeftMouseButton)) + AcceptDragging(); + else + CancelDragging(); + return; + case RawMouseEventType.MiddleButtonUp: + if (_initialInputModifiers.Value.HasFlag(InputModifiers.MiddleMouseButton)) + AcceptDragging(); + else + CancelDragging(); + return; + case RawMouseEventType.RightButtonUp: + if (_initialInputModifiers.Value.HasFlag(InputModifiers.RightMouseButton)) + AcceptDragging(); + else + CancelDragging(); + return; + case RawMouseEventType.Move: + var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS; + if (_initialInputModifiers.Value != mods) + { + CancelDragging(); + return; + } + + if (e.Root != _lastRoot) + { + if (_lastRoot != null) + RaiseDragEvent(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), allowedEffects); + RaiseDragEvent(RawDragEventType.DragEnter, e.Root, e.Position, allowedEffects); + _lastRoot = e.Root; + } + else + RaiseDragEvent(RawDragEventType.DragOver, e.Root, e.Position, allowedEffects); + return; + } + } + } +} diff --git a/src/Avalonia.Controls/Platform/IPlatformDragSource.cs b/src/Avalonia.Controls/Platform/IPlatformDragSource.cs new file mode 100644 index 0000000000..667e1f005c --- /dev/null +++ b/src/Avalonia.Controls/Platform/IPlatformDragSource.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.DragDrop; +using Avalonia.Interactivity; + +namespace Avalonia.Controls.Platform +{ + public interface IPlatformDragSource + { + Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects); + } +} diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs new file mode 100644 index 0000000000..937471f9ad --- /dev/null +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.DragDrop; +using Avalonia.Controls.Platform; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class DragSource : IPlatformDragSource + { + public Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + Dispatcher.UIThread.VerifyAccess(); + + OleDragSource src = new OleDragSource(); + DataObject dataObject = new DataObject(data); + int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects); + + int[] finalEffect = new int[1]; + UnmanagedMethods.DoDragDrop(dataObject, src, allowed, finalEffect); + + return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect[0]));} + } +} diff --git a/src/Windows/Avalonia.Win32/OleDragSource.cs b/src/Windows/Avalonia.Win32/OleDragSource.cs index 1bff984087..522014abc0 100644 --- a/src/Windows/Avalonia.Win32/OleDragSource.cs +++ b/src/Windows/Avalonia.Win32/OleDragSource.cs @@ -33,9 +33,7 @@ namespace Avalonia.Win32 public int GiveFeedback(int dwEffect) { - if (dwEffect != 0) - return DRAGDROP_S_USEDEFAULTCURSORS; - return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + return DRAGDROP_S_USEDEFAULTCURSORS; } } } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 31db5f88a9..747006673a 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -23,7 +23,7 @@ namespace Avalonia.Win32 _target = target; } - static DropEffect ConvertDropEffect(DragDropEffects operation) + public static DropEffect ConvertDropEffect(DragDropEffects operation) { DropEffect result = DropEffect.None; if (operation.HasFlag(DragDropEffects.Copy)) @@ -35,7 +35,7 @@ namespace Avalonia.Win32 return result; } - static DragDropEffects ConvertDropEffect(DropEffect effect) + public static DragDropEffects ConvertDropEffect(DropEffect effect) { DragDropEffects result = DragDropEffects.None; if (effect.HasFlag(DropEffect.Copy)) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 4e1ba618a8..ef1c4e987d 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -84,7 +84,8 @@ namespace Avalonia.Win32 .Bind().ToConstant(new RenderLoop(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) - .Bind().ToConstant(s_instance); + .Bind().ToConstant(s_instance) + .Bind().ToSingleton(); UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); From 81aaa0938309fab331725189c841b5b5bd35c247 Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 7 Mar 2018 18:43:07 +0100 Subject: [PATCH 16/32] set the cursor for the drop-effects --- src/Avalonia.Controls/DragDrop/DragSource.cs | 47 ++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/DragDrop/DragSource.cs b/src/Avalonia.Controls/DragDrop/DragSource.cs index f899eabfbd..2048a0c0da 100644 --- a/src/Avalonia.Controls/DragDrop/DragSource.cs +++ b/src/Avalonia.Controls/DragDrop/DragSource.cs @@ -23,8 +23,9 @@ namespace Avalonia.Controls.DragDrop private readonly Subject _result = new Subject(); private IDataObject _draggedData; - private IInputRoot _lastRoot; + private IInputElement _lastRoot; private InputModifiers? _initialInputModifiers; + private object _lastCursor; public DragSource() { @@ -54,9 +55,48 @@ namespace Avalonia.Controls.DragDrop RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, allowedEffects); var tl = root.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); tl.PlatformImpl.Input(rawEvent); + + SetCursor(root, rawEvent.Effects); return rawEvent.Effects; } + private Cursor GetCursorForDropEffect(DragDropEffects effects) + { + // Todo. Needs to choose cursor by effect. + if (effects == DragDropEffects.None) + return new Cursor(StandardCursorType.No); + return new Cursor(StandardCursorType.Hand); + } + + private void SetCursor(IInputElement root, DragDropEffects effect) + { + if (_lastRoot != root) + { + if (_lastRoot is InputElement ieLast) + { + if (_lastCursor == AvaloniaProperty.UnsetValue) + ieLast.ClearValue(InputElement.CursorProperty); + else + ieLast.Cursor = _lastCursor as Cursor; + } + + if (root is InputElement ieNew) + { + if (!ieNew.IsSet(InputElement.CursorProperty)) + _lastCursor = AvaloniaProperty.UnsetValue; + else + _lastCursor = root.Cursor; + } + else + _lastCursor = null; + + _lastRoot = root; + } + + if (root is InputElement ie) + ie.Cursor = GetCursorForDropEffect(effect); + } + private void ProcessMouseEvents(RawMouseEventArgs e, DragDropEffects allowedEffects) { if (!_initialInputModifiers.HasValue) @@ -66,16 +106,18 @@ namespace Avalonia.Controls.DragDrop { if (_lastRoot != null) RaiseDragEvent(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), allowedEffects); + SetCursor(null, DragDropEffects.None); _result.OnNext(DragDropEffects.None); e.Handled = true; } void AcceptDragging() { var result = RaiseDragEvent(RawDragEventType.Drop, e.Root, e.Position, allowedEffects) & allowedEffects; + SetCursor(null, DragDropEffects.None); _result.OnNext(result); e.Handled = true; } - + switch (e.Type) { case RawMouseEventType.LeftButtonDown: @@ -118,7 +160,6 @@ namespace Avalonia.Controls.DragDrop if (_lastRoot != null) RaiseDragEvent(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), allowedEffects); RaiseDragEvent(RawDragEventType.DragEnter, e.Root, e.Position, allowedEffects); - _lastRoot = e.Root; } else RaiseDragEvent(RawDragEventType.DragOver, e.Root, e.Position, allowedEffects); From 74163ff668d744a1c3b06917cdeef63977df89fd Mon Sep 17 00:00:00 2001 From: boombuler Date: Thu, 8 Mar 2018 15:03:51 +0100 Subject: [PATCH 17/32] Added D+D StandardCursors --- src/Avalonia.Input/Cursors.cs | 5 +++- src/Gtk/Avalonia.Gtk3/CursorFactory.cs | 5 +++- src/OSX/Avalonia.MonoMac/Cursor.cs | 4 +++ src/Windows/Avalonia.Win32/CursorFactory.cs | 27 +++++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursors.cs index e3860e58e5..02a026c998 100644 --- a/src/Avalonia.Input/Cursors.cs +++ b/src/Avalonia.Input/Cursors.cs @@ -38,7 +38,10 @@ namespace Avalonia.Input TopLeftCorner, TopRightCorner, BottomLeftCorner, - BottomRightCorner + BottomRightCorner, + DragMove, + DragCopy, + DragLink, // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image diff --git a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs index ac547b8bc2..d6a3c1f260 100644 --- a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs +++ b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs @@ -32,7 +32,10 @@ namespace Avalonia.Gtk3 {StandardCursorType.TopLeftCorner, CursorType.TopLeftCorner}, {StandardCursorType.TopRightCorner, CursorType.TopRightCorner}, {StandardCursorType.BottomLeftCorner, CursorType.BottomLeftCorner}, - {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner} + {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}, + {StandardCursorType.DragCopy, CursorType.CenterPtr}, + {StandardCursorType.DragMove, CursorType.Fleur}, + {StandardCursorType.DragLink, CursorType.Cross}, }; private static readonly Dictionary Cache = diff --git a/src/OSX/Avalonia.MonoMac/Cursor.cs b/src/OSX/Avalonia.MonoMac/Cursor.cs index 10445e62e2..d9370e527b 100644 --- a/src/OSX/Avalonia.MonoMac/Cursor.cs +++ b/src/OSX/Avalonia.MonoMac/Cursor.cs @@ -51,6 +51,10 @@ namespace Avalonia.MonoMac [StandardCursorType.TopSide] = NSCursor.ResizeUpCursor, [StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor, [StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO + [StandardCursorType.DragMove] = NSCursor.DragCopyCursor, // TODO + [StandardCursorType.DragCopy] = NSCursor.DragCopyCursor, + [StandardCursorType.DragLink] = NSCursor.DragLinkCursor, + }; } diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 0d529d6b91..fa2fbe4810 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Platform; +using System.Runtime.InteropServices; namespace Avalonia.Win32 { @@ -20,6 +21,27 @@ namespace Avalonia.Win32 { } + static CursorFactory() + { + LoadModuleCursor(StandardCursorType.DragMove, "ole32.dll", 2); + LoadModuleCursor(StandardCursorType.DragCopy, "ole32.dll", 3); + LoadModuleCursor(StandardCursorType.DragLink, "ole32.dll", 4); + } + + private static void LoadModuleCursor(StandardCursorType cursorType, string module, int id) + { + IntPtr mh = UnmanagedMethods.GetModuleHandle(module); + if (mh != IntPtr.Zero) + { + IntPtr cursor = UnmanagedMethods.LoadCursor(mh, new IntPtr(id)); + if (cursor != IntPtr.Zero) + { + PlatformHandle phCursor = new PlatformHandle(cursor, PlatformConstants.CursorHandleType); + Cache.Add(cursorType, phCursor); + } + } + } + private static readonly Dictionary CursorTypeMapping = new Dictionary { @@ -47,6 +69,11 @@ namespace Avalonia.Win32 //Using SizeNorthEastSouthWest {StandardCursorType.TopRightCorner, 32643}, {StandardCursorType.BottomLeftCorner, 32643}, + + // Fallback, should have been loaded from ole32.dll + {StandardCursorType.DragMove, 32516}, + {StandardCursorType.DragCopy, 32516}, + {StandardCursorType.DragLink, 32516}, }; private static readonly Dictionary Cache = From 97f581af496fc8172b8e1863bb6fae89e8f544dc Mon Sep 17 00:00:00 2001 From: boombuler Date: Thu, 8 Mar 2018 15:05:13 +0100 Subject: [PATCH 18/32] Improved DragSource the drag source now gets the preferred drag effect and handles keyboard inputs too --- src/Avalonia.Controls/DragDrop/DragSource.cs | 129 ++++++++++++------- 1 file changed, 81 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.Controls/DragDrop/DragSource.cs b/src/Avalonia.Controls/DragDrop/DragSource.cs index 2048a0c0da..fd3d8ef143 100644 --- a/src/Avalonia.Controls/DragDrop/DragSource.cs +++ b/src/Avalonia.Controls/DragDrop/DragSource.cs @@ -19,13 +19,14 @@ namespace Avalonia.Controls.DragDrop private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton; private readonly IDragDropDevice _dragDrop; private readonly IInputManager _inputManager; - - private readonly Subject _result = new Subject(); + + private DragDropEffects _allowedEffects; private IDataObject _draggedData; private IInputElement _lastRoot; - private InputModifiers? _initialInputModifiers; + private Point _lastPosition; private object _lastCursor; + private InputModifiers? _initialInputModifiers; public DragSource() { @@ -40,35 +41,58 @@ namespace Avalonia.Controls.DragDrop { _draggedData = data; _lastRoot = null; + _lastPosition = default(Point); + _allowedEffects = allowedEffects; - using (_inputManager.PreProcess.OfType().Subscribe(e => ProcessMouseEvents(e, allowedEffects))) + using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) { - var effect = await _result.FirstAsync(); - return effect; + using (_inputManager.PreProcess.OfType().Subscribe(ProcessKeyEvents)) + { + var effect = await _result.FirstAsync(); + return effect; + } } } return DragDropEffects.None; } - private DragDropEffects RaiseDragEvent(RawDragEventType type, IInputElement root, Point pt, DragDropEffects allowedEffects) + + private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers) { - RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, allowedEffects); + _lastPosition = pt; + + RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects); var tl = root.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); tl.PlatformImpl.Input(rawEvent); - - SetCursor(root, rawEvent.Effects); - return rawEvent.Effects; + + var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers); + UpdateCursor(root, effect); + return effect; + } + + private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers) + { + if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None) + return effect; // No need to check for the modifiers. + if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt)) + return DragDropEffects.Link; + if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control)) + return DragDropEffects.Copy; + return DragDropEffects.Move; } private Cursor GetCursorForDropEffect(DragDropEffects effects) { - // Todo. Needs to choose cursor by effect. - if (effects == DragDropEffects.None) - return new Cursor(StandardCursorType.No); - return new Cursor(StandardCursorType.Hand); + if (effects.HasFlag(DragDropEffects.Copy)) + return new Cursor(StandardCursorType.DragCopy); + if (effects.HasFlag(DragDropEffects.Move)) + return new Cursor(StandardCursorType.DragMove); + if (effects.HasFlag(DragDropEffects.Link)) + return new Cursor(StandardCursorType.DragLink); + return new Cursor(StandardCursorType.No); } - private void SetCursor(IInputElement root, DragDropEffects effect) + private void UpdateCursor(IInputElement root, DragDropEffects effect) { if (_lastRoot != root) { @@ -96,25 +120,45 @@ namespace Avalonia.Controls.DragDrop if (root is InputElement ie) ie.Cursor = GetCursorForDropEffect(effect); } - - private void ProcessMouseEvents(RawMouseEventArgs e, DragDropEffects allowedEffects) + + private void CancelDragging() { - if (!_initialInputModifiers.HasValue) - _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS; + if (_lastRoot != null) + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None); + UpdateCursor(null, DragDropEffects.None); + _result.OnNext(DragDropEffects.None); + } - void CancelDragging() + private void ProcessKeyEvents(RawKeyEventArgs e) + { + if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape) { if (_lastRoot != null) - RaiseDragEvent(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), allowedEffects); - SetCursor(null, DragDropEffects.None); + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers); + UpdateCursor(null, DragDropEffects.None); _result.OnNext(DragDropEffects.None); e.Handled = true; } - void AcceptDragging() + else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt) + RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers); + } + + private void ProcessMouseEvents(RawMouseEventArgs e) + { + if (!_initialInputModifiers.HasValue) + _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS; + + + void CheckDraggingAccepted(InputModifiers changedMouseButton) { - var result = RaiseDragEvent(RawDragEventType.Drop, e.Root, e.Position, allowedEffects) & allowedEffects; - SetCursor(null, DragDropEffects.None); - _result.OnNext(result); + if (_initialInputModifiers.Value.HasFlag(changedMouseButton)) + { + var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers); + UpdateCursor(null, DragDropEffects.None); + _result.OnNext(result); + } + else + CancelDragging(); e.Handled = true; } @@ -125,45 +169,34 @@ namespace Avalonia.Controls.DragDrop case RawMouseEventType.MiddleButtonDown: case RawMouseEventType.NonClientLeftButtonDown: CancelDragging(); + e.Handled = true; return; case RawMouseEventType.LeaveWindow: - RaiseDragEvent(RawDragEventType.DragLeave, e.Root, e.Position, allowedEffects); - break; + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break; case RawMouseEventType.LeftButtonUp: - if (_initialInputModifiers.Value.HasFlag(InputModifiers.LeftMouseButton)) - AcceptDragging(); - else - CancelDragging(); - return; + CheckDraggingAccepted(InputModifiers.LeftMouseButton); break; case RawMouseEventType.MiddleButtonUp: - if (_initialInputModifiers.Value.HasFlag(InputModifiers.MiddleMouseButton)) - AcceptDragging(); - else - CancelDragging(); - return; + CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break; case RawMouseEventType.RightButtonUp: - if (_initialInputModifiers.Value.HasFlag(InputModifiers.RightMouseButton)) - AcceptDragging(); - else - CancelDragging(); - return; + CheckDraggingAccepted(InputModifiers.RightMouseButton); break; case RawMouseEventType.Move: var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS; if (_initialInputModifiers.Value != mods) { CancelDragging(); + e.Handled = true; return; } if (e.Root != _lastRoot) { if (_lastRoot != null) - RaiseDragEvent(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), allowedEffects); - RaiseDragEvent(RawDragEventType.DragEnter, e.Root, e.Position, allowedEffects); + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers); + RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers); } else - RaiseDragEvent(RawDragEventType.DragOver, e.Root, e.Position, allowedEffects); - return; + RaiseEventAndUpdateCursor(RawDragEventType.DragOver, e.Root, e.Position, e.InputModifiers); + break; } } } From ed4a85fef992f16e5c84554c2a2c8caee8c8ffcf Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 9 Mar 2018 16:41:41 +0100 Subject: [PATCH 19/32] Allow Winforms + WPF compatible object drag --- src/Windows/Avalonia.Win32/DataObject.cs | 51 ++++++++++++++++++++- src/Windows/Avalonia.Win32/OleDataObject.cs | 26 ++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 6248afd7bb..aa3ee91ef9 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -8,11 +8,16 @@ using Avalonia.Controls.DragDrop; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Controls.DragDrop.IDataObject; using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; namespace Avalonia.Win32 { class DataObject : IDataObject, IOleDataObject { + // Compatibility with WinForms + WPF... + internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray(); + class FormatEnumerator : IEnumFORMATETC { private FORMATETC[] _formats; @@ -249,7 +254,51 @@ namespace Avalonia.Win32 return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data)); if (dataFormat == DataFormats.FileNames && data is IEnumerable files) return WriteFileListToHGlobal(ref hGlobal, files); - return DV_E_TYMED; + if (data is Stream stream) + { + byte[] buffer = new byte[stream.Length - stream.Position]; + stream.Read(buffer, 0, buffer.Length); + return WriteBytesToHGlobal(ref hGlobal, buffer); + } + if (data is IEnumerable bytes) + { + var byteArr = bytes is byte[] ? (byte[])bytes : bytes.ToArray(); + return WriteBytesToHGlobal(ref hGlobal, byteArr); + } + return WriteBytesToHGlobal(ref hGlobal, SerializeObject(data)); + } + + private byte[] SerializeObject(object data) + { + using (var ms = new MemoryStream()) + { + ms.Write(SerializedObjectGUID, 0, SerializedObjectGUID.Length); + BinaryFormatter binaryFormatter = new BinaryFormatter(); + binaryFormatter.Serialize(ms, data); + return ms.ToArray(); + } + } + + private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data) + { + int required = data.Length; + if (hGlobal == IntPtr.Zero) + hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required); + + long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + if (required > available) + return STG_E_MEDIUMFULL; + + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + Marshal.Copy(data, 0, ptr, data.Length); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } } private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable files) diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index fe6faa162c..60527bb7d6 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; +using System.Runtime.Serialization.Formatters.Binary; using System.Text; using Avalonia.Controls.DragDrop; using Avalonia.Win32.Interop; @@ -62,7 +64,19 @@ namespace Avalonia.Win32 return ReadStringFromHGlobal(medium.unionmember); if (format == DataFormats.FileNames) return ReadFileNamesFromHGlobal(medium.unionmember); - return ReadBytesFromHGlobal(medium.unionmember); + + byte[] data = ReadBytesFromHGlobal(medium.unionmember); + + if (IsSerializedObject(data)) + { + using (var ms = new MemoryStream(data)) + { + ms.Position = DataObject.SerializedObjectGUID.Length; + BinaryFormatter binaryFormatter = new BinaryFormatter(); + return binaryFormatter.Deserialize(ms); + } + } + return data; } } finally @@ -73,6 +87,16 @@ namespace Avalonia.Win32 return null; } + private bool IsSerializedObject(byte[] data) + { + if (data.Length < DataObject.SerializedObjectGUID.Length) + return false; + for (int i = 0; i < DataObject.SerializedObjectGUID.Length; i++) + if (data[i] != DataObject.SerializedObjectGUID[i]) + return false; + return true; + } + private static IEnumerable ReadFileNamesFromHGlobal(IntPtr hGlobal) { List files = new List(); From cfb3924c0fa645c6256b32af93a8000a3a936b92 Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 9 Mar 2018 16:42:06 +0100 Subject: [PATCH 20/32] Prevent setting the cursor to often. --- src/Avalonia.Controls/DragDrop/DragSource.cs | 33 +++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/DragDrop/DragSource.cs b/src/Avalonia.Controls/DragDrop/DragSource.cs index fd3d8ef143..ceb962ad98 100644 --- a/src/Avalonia.Controls/DragDrop/DragSource.cs +++ b/src/Avalonia.Controls/DragDrop/DragSource.cs @@ -25,7 +25,8 @@ namespace Avalonia.Controls.DragDrop private IDataObject _draggedData; private IInputElement _lastRoot; private Point _lastPosition; - private object _lastCursor; + private StandardCursorType _lastCursorType; + private object _originalCursor; private InputModifiers? _initialInputModifiers; public DragSource() @@ -81,15 +82,15 @@ namespace Avalonia.Controls.DragDrop return DragDropEffects.Move; } - private Cursor GetCursorForDropEffect(DragDropEffects effects) + private StandardCursorType GetCursorForDropEffect(DragDropEffects effects) { if (effects.HasFlag(DragDropEffects.Copy)) - return new Cursor(StandardCursorType.DragCopy); + return StandardCursorType.DragCopy; if (effects.HasFlag(DragDropEffects.Move)) - return new Cursor(StandardCursorType.DragMove); + return StandardCursorType.DragMove; if (effects.HasFlag(DragDropEffects.Link)) - return new Cursor(StandardCursorType.DragLink); - return new Cursor(StandardCursorType.No); + return StandardCursorType.DragLink; + return StandardCursorType.No; } private void UpdateCursor(IInputElement root, DragDropEffects effect) @@ -98,27 +99,35 @@ namespace Avalonia.Controls.DragDrop { if (_lastRoot is InputElement ieLast) { - if (_lastCursor == AvaloniaProperty.UnsetValue) + if (_originalCursor == AvaloniaProperty.UnsetValue) ieLast.ClearValue(InputElement.CursorProperty); else - ieLast.Cursor = _lastCursor as Cursor; + ieLast.Cursor = _originalCursor as Cursor; } if (root is InputElement ieNew) { if (!ieNew.IsSet(InputElement.CursorProperty)) - _lastCursor = AvaloniaProperty.UnsetValue; + _originalCursor = AvaloniaProperty.UnsetValue; else - _lastCursor = root.Cursor; + _originalCursor = root.Cursor; } else - _lastCursor = null; + _originalCursor = null; + _lastCursorType = StandardCursorType.Arrow; _lastRoot = root; } if (root is InputElement ie) - ie.Cursor = GetCursorForDropEffect(effect); + { + var ct = GetCursorForDropEffect(effect); + if (ct != _lastCursorType) + { + _lastCursorType = ct; + ie.Cursor = new Cursor(ct); + } + } } private void CancelDragging() From c7fd7183bd13ab9aca6533909273a4ae2ac85c5b Mon Sep 17 00:00:00 2001 From: boombuler Date: Sun, 11 Mar 2018 09:56:52 +0100 Subject: [PATCH 21/32] basic dragging support for OSX --- src/OSX/Avalonia.MonoMac/DragSource.cs | 81 +++++++++++++++++++++ src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 3 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/OSX/Avalonia.MonoMac/DragSource.cs diff --git a/src/OSX/Avalonia.MonoMac/DragSource.cs b/src/OSX/Avalonia.MonoMac/DragSource.cs new file mode 100644 index 0000000000..ea1739e33a --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/DragSource.cs @@ -0,0 +1,81 @@ +using System.Data; +using System.Linq.Expressions; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.DragDrop; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Raw; +using MonoMac.AppKit; +using MonoMac.CoreGraphics; +using MonoMac.Foundation; +using MonoMac.OpenGL; + +namespace Avalonia.MonoMac +{ + public class DragSource : NSDraggingSource, IPlatformDragSource + { + class DS : NSDraggingSource + { + private readonly DragDropEffects _allowedEffects; + public ReplaySubject Result { get; } = new ReplaySubject(); + + public DS(DragDropEffects allowedEffects) + { + _allowedEffects = allowedEffects; + } + + public override bool IgnoreModifierKeysWhileDragging => false; + + public override NSDragOperation DraggingSourceOperationMaskForLocal(bool flag) + { + return DraggingInfo.ConvertDragOperation(_allowedEffects); + } + + public override void DraggedImageEndedAtOperation(NSImage image, CGPoint screenPoint, NSDragOperation operation) + { + Result.OnNext(DraggingInfo.ConvertDragOperation(operation)); + Result.OnCompleted(); + } + } + + + + private readonly IInputManager _inputManager; + + + public DragSource() + { + _inputManager = AvaloniaLocator.Current.GetService(); + } + + private NSDraggingItem[] ConvertDraggedItems(IDataObject data) + { + NSString s = new NSString("FOOBAR"); + NSPasteboardWriting w = new NSPasteboardWriting(s.Handle); + var text = new NSDraggingItem(w); + return new[] {text}; + } + + + public async Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + // We need the TopLevelImpl + a mouse location so we just wait for the next event. + var mouseEv = await _inputManager.PreProcess.OfType().FirstAsync(); + var view = ((mouseEv.Root as TopLevel)?.PlatformImpl as TopLevelImpl)?.View; + if (view == null) + return DragDropEffects.None; + + // Prepare the source event: + var pt = view.TranslateLocalPoint(mouseEv.Position).ToMonoMacPoint(); + var ev = NSEvent.MouseEvent(NSEventType.LeftMouseDown, pt, 0, 0, 0, null, 0, 0, 0); + + var ds = new DS(allowedEffects); + view.BeginDraggingSession(ConvertDraggedItems(data) ,ev, ds); + + return await ds.Result; + } + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index 5907459459..3db02de209 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -35,7 +35,8 @@ namespace Avalonia.MonoMac .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(s_renderLoop) - .Bind().ToConstant(PlatformThreadingInterface.Instance); + .Bind().ToConstant(PlatformThreadingInterface.Instance) + .Bind().ToTransient(); } public static void Initialize() From 1afb52b27d08ac10412f4945b09196f4a399c25a Mon Sep 17 00:00:00 2001 From: boombuler Date: Sun, 11 Mar 2018 16:18:39 +0100 Subject: [PATCH 22/32] OSX drag cleanup --- src/OSX/Avalonia.MonoMac/DragSource.cs | 96 ++++++++++++++++---------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/DragSource.cs b/src/OSX/Avalonia.MonoMac/DragSource.cs index ea1739e33a..77bbee86f1 100644 --- a/src/OSX/Avalonia.MonoMac/DragSource.cs +++ b/src/OSX/Avalonia.MonoMac/DragSource.cs @@ -1,13 +1,19 @@ -using System.Data; +using System; +using System.Data; +using System.IO; +using System.Linq; using System.Linq.Expressions; using System.Reactive.Linq; using System.Reactive.Subjects; +using System.Runtime.InteropServices; +using System.Runtime.Serialization.Formatters.Binary; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.DragDrop; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; +using MonoMac; using MonoMac.AppKit; using MonoMac.CoreGraphics; using MonoMac.Foundation; @@ -17,46 +23,54 @@ namespace Avalonia.MonoMac { public class DragSource : NSDraggingSource, IPlatformDragSource { - class DS : NSDraggingSource - { - private readonly DragDropEffects _allowedEffects; - public ReplaySubject Result { get; } = new ReplaySubject(); - - public DS(DragDropEffects allowedEffects) - { - _allowedEffects = allowedEffects; - } - - public override bool IgnoreModifierKeysWhileDragging => false; - - public override NSDragOperation DraggingSourceOperationMaskForLocal(bool flag) - { - return DraggingInfo.ConvertDragOperation(_allowedEffects); - } - - public override void DraggedImageEndedAtOperation(NSImage image, CGPoint screenPoint, NSDragOperation operation) - { - Result.OnNext(DraggingInfo.ConvertDragOperation(operation)); - Result.OnCompleted(); - } - } - - + private const string NSPasteboardTypeString = "public.utf8-plain-text"; + private readonly Subject _result = new Subject(); private readonly IInputManager _inputManager; + private DragDropEffects _allowedEffects; - + public override bool IgnoreModifierKeysWhileDragging => false; + public DragSource() { _inputManager = AvaloniaLocator.Current.GetService(); } - private NSDraggingItem[] ConvertDraggedItems(IDataObject data) + private string DataFormatToUTI(string s) + { + if (s == DataFormats.FileNames) + throw new NotImplementedException(); + if (s == DataFormats.Text) + return NSPasteboardTypeString; + return s; + } + + private NSDraggingItem CreateDraggingItem(string format, object data) { - NSString s = new NSString("FOOBAR"); - NSPasteboardWriting w = new NSPasteboardWriting(s.Handle); - var text = new NSDraggingItem(w); - return new[] {text}; + + var pasteboardItem = new NSPasteboardItem(); + NSData nsData; + if(data is string s) + nsData = NSData.FromString(s); + else if (data is Stream strm) + nsData = NSData.FromStream(strm); + else if (data is byte[] bytes) + nsData = NSData.FromArray(bytes); + else + { + BinaryFormatter bf = new BinaryFormatter(); + using (var ms = new MemoryStream()) + { + bf.Serialize(ms, data); + ms.Position = 0; + nsData = NSData.FromStream(ms); + } + } + pasteboardItem.SetDataForType(nsData, DataFormatToUTI(format)); + + NSPasteboardWriting writing = new NSPasteboardWriting(pasteboardItem.Handle); + + return new NSDraggingItem(writing); } @@ -72,10 +86,22 @@ namespace Avalonia.MonoMac var pt = view.TranslateLocalPoint(mouseEv.Position).ToMonoMacPoint(); var ev = NSEvent.MouseEvent(NSEventType.LeftMouseDown, pt, 0, 0, 0, null, 0, 0, 0); - var ds = new DS(allowedEffects); - view.BeginDraggingSession(ConvertDraggedItems(data) ,ev, ds); + _allowedEffects = allowedEffects; + var items = data.GetDataFormats().Select(fmt => CreateDraggingItem(fmt, data.Get(fmt))).ToArray(); + view.BeginDraggingSession(items ,ev, this); - return await ds.Result; + return await _result; + } + + public override NSDragOperation DraggingSourceOperationMaskForLocal(bool flag) + { + return DraggingInfo.ConvertDragOperation(_allowedEffects); + } + + public override void DraggedImageEndedAtOperation(NSImage image, CGPoint screenPoint, NSDragOperation operation) + { + _result.OnNext(DraggingInfo.ConvertDragOperation(operation)); + _result.OnCompleted(); } } } \ No newline at end of file From 772ce18ca487d4949ef9bd0e80c2e25b7dab47b3 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sun, 11 Mar 2018 18:23:41 +0100 Subject: [PATCH 23/32] drag file urls to finder. --- src/OSX/Avalonia.MonoMac/DragSource.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/DragSource.cs b/src/OSX/Avalonia.MonoMac/DragSource.cs index 77bbee86f1..2275c7505f 100644 --- a/src/OSX/Avalonia.MonoMac/DragSource.cs +++ b/src/OSX/Avalonia.MonoMac/DragSource.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; @@ -24,6 +25,7 @@ namespace Avalonia.MonoMac public class DragSource : NSDraggingSource, IPlatformDragSource { private const string NSPasteboardTypeString = "public.utf8-plain-text"; + private const string NSPasteboardTypeFileUrl = "public.file-url"; private readonly Subject _result = new Subject(); private readonly IInputManager _inputManager; @@ -39,7 +41,7 @@ namespace Avalonia.MonoMac private string DataFormatToUTI(string s) { if (s == DataFormats.FileNames) - throw new NotImplementedException(); + return NSPasteboardTypeFileUrl; if (s == DataFormats.Text) return NSPasteboardTypeString; return s; @@ -47,7 +49,6 @@ namespace Avalonia.MonoMac private NSDraggingItem CreateDraggingItem(string format, object data) { - var pasteboardItem = new NSPasteboardItem(); NSData nsData; if(data is string s) @@ -72,6 +73,19 @@ namespace Avalonia.MonoMac return new NSDraggingItem(writing); } + + public IEnumerable CreateDraggingItems(string format, object data) + { + if (format == DataFormats.FileNames && data is IEnumerable files) + { + foreach (var file in files) + yield return CreateDraggingItem(format, file); + + yield break; + } + + yield return CreateDraggingItem(format, data); + } public async Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) @@ -87,7 +101,7 @@ namespace Avalonia.MonoMac var ev = NSEvent.MouseEvent(NSEventType.LeftMouseDown, pt, 0, 0, 0, null, 0, 0, 0); _allowedEffects = allowedEffects; - var items = data.GetDataFormats().Select(fmt => CreateDraggingItem(fmt, data.Get(fmt))).ToArray(); + var items = data.GetDataFormats().SelectMany(fmt => CreateDraggingItems(fmt, data.Get(fmt))).ToArray(); view.BeginDraggingSession(items ,ev, this); return await _result; From 52422afe8bce50558ff8a8a2d7397bfa44b9f5e2 Mon Sep 17 00:00:00 2001 From: boombuler Date: Sun, 11 Mar 2018 18:28:00 +0100 Subject: [PATCH 24/32] automatically ensure dragged filenames are uris --- src/OSX/Avalonia.MonoMac/DragSource.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OSX/Avalonia.MonoMac/DragSource.cs b/src/OSX/Avalonia.MonoMac/DragSource.cs index 2275c7505f..07f2e186aa 100644 --- a/src/OSX/Avalonia.MonoMac/DragSource.cs +++ b/src/OSX/Avalonia.MonoMac/DragSource.cs @@ -51,8 +51,12 @@ namespace Avalonia.MonoMac { var pasteboardItem = new NSPasteboardItem(); NSData nsData; - if(data is string s) + if (data is string s) + { + if (format == DataFormats.FileNames) + s = new Uri(s).AbsoluteUri; // Ensure file uris... nsData = NSData.FromString(s); + } else if (data is Stream strm) nsData = NSData.FromStream(strm); else if (data is byte[] bytes) From 8713cd5c9f13e1e6e6b0b662cc9151350cb6de3b Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 11 Apr 2018 07:34:16 +0200 Subject: [PATCH 25/32] code cleanup --- src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs b/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs index e9c1119e44..c2bc1d5712 100644 --- a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs +++ b/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.DragDrop.Raw public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type, IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects) - :base(inputDevice, GetTimeStamp()) + :base(inputDevice, 0) { Type = type; InputRoot = inputRoot; @@ -22,10 +22,5 @@ namespace Avalonia.Controls.DragDrop.Raw Data = data; Effects = effects; } - - private static uint GetTimeStamp() - { - return (uint)0; - } } } \ No newline at end of file From 7032b9b8eb64ce7ffddbccb5f161d45429e43a86 Mon Sep 17 00:00:00 2001 From: boombuler Date: Wed, 11 Apr 2018 08:11:33 +0200 Subject: [PATCH 26/32] moved Drag+Drop sources to Avalonia.Input Also the windows specific DragSource is only used when the application runs as STA --- src/Avalonia.Controls/Application.cs | 5 +++-- .../{DragDrop => Platform}/DragSource.cs | 10 +++++----- .../DragDrop/DataFormats.cs | 2 +- .../DragDrop/DataObject.cs | 2 +- .../DragDrop/DragDrop.cs | 4 ++-- .../DragDrop/DragDropDevice.cs | 9 ++++----- .../DragDrop/DragDropEffects.cs | 2 +- .../DragDrop/DragEventArgs.cs | 2 +- .../DragDrop/IDataObject.cs | 2 +- .../DragDrop/Raw/IDragDropDevice.cs | 2 +- .../DragDrop/Raw/RawDragEvent.cs | 2 +- .../DragDrop/Raw/RawDragEventType.cs | 2 +- .../Platform/IPlatformDragSource.cs | 4 ++-- src/OSX/Avalonia.MonoMac/DragSource.cs | 4 ++-- src/OSX/Avalonia.MonoMac/DraggingInfo.cs | 2 +- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 4 ++-- src/Windows/Avalonia.Win32/ClipboardFormats.cs | 2 +- src/Windows/Avalonia.Win32/DataObject.cs | 4 ++-- src/Windows/Avalonia.Win32/DragSource.cs | 5 ++--- src/Windows/Avalonia.Win32/OleDataObject.cs | 4 ++-- src/Windows/Avalonia.Win32/OleDropTarget.cs | 6 +++--- src/Windows/Avalonia.Win32/Win32Platform.cs | 6 ++++-- 22 files changed, 43 insertions(+), 42 deletions(-) rename src/Avalonia.Controls/{DragDrop => Platform}/DragSource.cs (98%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/DataFormats.cs (89%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/DataObject.cs (96%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/DragDrop.cs (96%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/DragDropDevice.cs (95%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/DragDropEffects.cs (79%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/DragEventArgs.cs (90%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/IDataObject.cs (88%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/Raw/IDragDropDevice.cs (68%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/Raw/RawDragEvent.cs (94%) rename src/{Avalonia.Controls => Avalonia.Input}/DragDrop/Raw/RawDragEventType.cs (72%) rename src/{Avalonia.Controls => Avalonia.Input}/Platform/IPlatformDragSource.cs (79%) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6ed797dc51..f94a85f31d 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -12,9 +12,10 @@ using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; using System.Reactive.Concurrency; -using Avalonia.Controls.DragDrop; -using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Input.DragDrop.Raw; using Avalonia.Controls.Platform; +using Avalonia.Platform; +using Avalonia.Input.DragDrop; namespace Avalonia { diff --git a/src/Avalonia.Controls/DragDrop/DragSource.cs b/src/Avalonia.Controls/Platform/DragSource.cs similarity index 98% rename from src/Avalonia.Controls/DragDrop/DragSource.cs rename to src/Avalonia.Controls/Platform/DragSource.cs index ceb962ad98..ed873ec961 100644 --- a/src/Avalonia.Controls/DragDrop/DragSource.cs +++ b/src/Avalonia.Controls/Platform/DragSource.cs @@ -2,17 +2,17 @@ using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Text; using System.Threading.Tasks; -using Avalonia.Controls.DragDrop.Raw; -using Avalonia.Controls.Platform; +using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.DragDrop; +using Avalonia.Input.DragDrop.Raw; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; -using Avalonia.Interactivity; using Avalonia.Threading; using Avalonia.VisualTree; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Platform { class DragSource : IPlatformDragSource { diff --git a/src/Avalonia.Controls/DragDrop/DataFormats.cs b/src/Avalonia.Input/DragDrop/DataFormats.cs similarity index 89% rename from src/Avalonia.Controls/DragDrop/DataFormats.cs rename to src/Avalonia.Input/DragDrop/DataFormats.cs index f46651ed3b..4115701478 100644 --- a/src/Avalonia.Controls/DragDrop/DataFormats.cs +++ b/src/Avalonia.Input/DragDrop/DataFormats.cs @@ -1,4 +1,4 @@ -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { public static class DataFormats { diff --git a/src/Avalonia.Controls/DragDrop/DataObject.cs b/src/Avalonia.Input/DragDrop/DataObject.cs similarity index 96% rename from src/Avalonia.Controls/DragDrop/DataObject.cs rename to src/Avalonia.Input/DragDrop/DataObject.cs index e0e7b6d069..0db1008a6c 100644 --- a/src/Avalonia.Controls/DragDrop/DataObject.cs +++ b/src/Avalonia.Input/DragDrop/DataObject.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { public class DataObject : IDataObject { diff --git a/src/Avalonia.Controls/DragDrop/DragDrop.cs b/src/Avalonia.Input/DragDrop/DragDrop.cs similarity index 96% rename from src/Avalonia.Controls/DragDrop/DragDrop.cs rename to src/Avalonia.Input/DragDrop/DragDrop.cs index bbac80e38f..9d68deb62b 100644 --- a/src/Avalonia.Controls/DragDrop/DragDrop.cs +++ b/src/Avalonia.Input/DragDrop/DragDrop.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; -using Avalonia.Controls.Platform; using Avalonia.Interactivity; +using Avalonia.Input.Platform; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { public static class DragDrop { diff --git a/src/Avalonia.Controls/DragDrop/DragDropDevice.cs b/src/Avalonia.Input/DragDrop/DragDropDevice.cs similarity index 95% rename from src/Avalonia.Controls/DragDrop/DragDropDevice.cs rename to src/Avalonia.Input/DragDrop/DragDropDevice.cs index 0aadda79f4..0a64800346 100644 --- a/src/Avalonia.Controls/DragDrop/DragDropDevice.cs +++ b/src/Avalonia.Input/DragDrop/DragDropDevice.cs @@ -1,13 +1,12 @@ -using Avalonia.Input; -using Avalonia.Interactivity; +using Avalonia.Interactivity; using Avalonia.VisualTree; using System.Linq; -using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Input.DragDrop.Raw; using Avalonia.Input.Raw; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { - class DragDropDevice : IDragDropDevice + public class DragDropDevice : IDragDropDevice { public static readonly DragDropDevice Instance = new DragDropDevice(); diff --git a/src/Avalonia.Controls/DragDrop/DragDropEffects.cs b/src/Avalonia.Input/DragDrop/DragDropEffects.cs similarity index 79% rename from src/Avalonia.Controls/DragDrop/DragDropEffects.cs rename to src/Avalonia.Input/DragDrop/DragDropEffects.cs index cec3dab89d..7f093bc89c 100644 --- a/src/Avalonia.Controls/DragDrop/DragDropEffects.cs +++ b/src/Avalonia.Input/DragDrop/DragDropEffects.cs @@ -1,6 +1,6 @@ using System; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { [Flags] public enum DragDropEffects diff --git a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs b/src/Avalonia.Input/DragDrop/DragEventArgs.cs similarity index 90% rename from src/Avalonia.Controls/DragDrop/DragEventArgs.cs rename to src/Avalonia.Input/DragDrop/DragEventArgs.cs index f31bfd4820..bff19c760c 100644 --- a/src/Avalonia.Controls/DragDrop/DragEventArgs.cs +++ b/src/Avalonia.Input/DragDrop/DragEventArgs.cs @@ -1,6 +1,6 @@ using Avalonia.Interactivity; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { public class DragEventArgs : RoutedEventArgs { diff --git a/src/Avalonia.Controls/DragDrop/IDataObject.cs b/src/Avalonia.Input/DragDrop/IDataObject.cs similarity index 88% rename from src/Avalonia.Controls/DragDrop/IDataObject.cs rename to src/Avalonia.Input/DragDrop/IDataObject.cs index c8fd720c48..5d26a20cee 100644 --- a/src/Avalonia.Controls/DragDrop/IDataObject.cs +++ b/src/Avalonia.Input/DragDrop/IDataObject.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Avalonia.Controls.DragDrop +namespace Avalonia.Input.DragDrop { public interface IDataObject { diff --git a/src/Avalonia.Controls/DragDrop/Raw/IDragDropDevice.cs b/src/Avalonia.Input/DragDrop/Raw/IDragDropDevice.cs similarity index 68% rename from src/Avalonia.Controls/DragDrop/Raw/IDragDropDevice.cs rename to src/Avalonia.Input/DragDrop/Raw/IDragDropDevice.cs index 6e2db67ac3..2022e842ae 100644 --- a/src/Avalonia.Controls/DragDrop/Raw/IDragDropDevice.cs +++ b/src/Avalonia.Input/DragDrop/Raw/IDragDropDevice.cs @@ -1,6 +1,6 @@ using Avalonia.Input; -namespace Avalonia.Controls.DragDrop.Raw +namespace Avalonia.Input.DragDrop.Raw { public interface IDragDropDevice : IInputDevice { diff --git a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs b/src/Avalonia.Input/DragDrop/Raw/RawDragEvent.cs similarity index 94% rename from src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs rename to src/Avalonia.Input/DragDrop/Raw/RawDragEvent.cs index c2bc1d5712..af18186d4b 100644 --- a/src/Avalonia.Controls/DragDrop/Raw/RawDragEvent.cs +++ b/src/Avalonia.Input/DragDrop/Raw/RawDragEvent.cs @@ -2,7 +2,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; -namespace Avalonia.Controls.DragDrop.Raw +namespace Avalonia.Input.DragDrop.Raw { public class RawDragEvent : RawInputEventArgs { diff --git a/src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs b/src/Avalonia.Input/DragDrop/Raw/RawDragEventType.cs similarity index 72% rename from src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs rename to src/Avalonia.Input/DragDrop/Raw/RawDragEventType.cs index 44c4bbd595..31450efb51 100644 --- a/src/Avalonia.Controls/DragDrop/Raw/RawDragEventType.cs +++ b/src/Avalonia.Input/DragDrop/Raw/RawDragEventType.cs @@ -1,4 +1,4 @@ -namespace Avalonia.Controls.DragDrop.Raw +namespace Avalonia.Input.DragDrop.Raw { public enum RawDragEventType { diff --git a/src/Avalonia.Controls/Platform/IPlatformDragSource.cs b/src/Avalonia.Input/Platform/IPlatformDragSource.cs similarity index 79% rename from src/Avalonia.Controls/Platform/IPlatformDragSource.cs rename to src/Avalonia.Input/Platform/IPlatformDragSource.cs index 667e1f005c..44b4d30760 100644 --- a/src/Avalonia.Controls/Platform/IPlatformDragSource.cs +++ b/src/Avalonia.Input/Platform/IPlatformDragSource.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; -using Avalonia.Controls.DragDrop; +using Avalonia.Input.DragDrop; using Avalonia.Interactivity; -namespace Avalonia.Controls.Platform +namespace Avalonia.Input.Platform { public interface IPlatformDragSource { diff --git a/src/OSX/Avalonia.MonoMac/DragSource.cs b/src/OSX/Avalonia.MonoMac/DragSource.cs index 07f2e186aa..96d650ab3a 100644 --- a/src/OSX/Avalonia.MonoMac/DragSource.cs +++ b/src/OSX/Avalonia.MonoMac/DragSource.cs @@ -10,8 +10,8 @@ using System.Runtime.InteropServices; using System.Runtime.Serialization.Formatters.Binary; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Controls.DragDrop; -using Avalonia.Controls.Platform; +using Avalonia.Input.DragDrop; +using Avalonia.Input.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using MonoMac; diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs index 2fb28a50f5..ca8a24ba82 100644 --- a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Controls.DragDrop; +using Avalonia.Input.DragDrop; using MonoMac.AppKit; using MonoMac.Foundation; diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 97c4302c57..34d0949b57 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Avalonia.Controls.DragDrop; -using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Input.DragDrop; +using Avalonia.Input.DragDrop.Raw; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index a59c6a1664..a604f634f6 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; -using Avalonia.Controls.DragDrop; +using Avalonia.Input.DragDrop; using Avalonia.Win32.Interop; namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index aa3ee91ef9..2a990419e7 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; -using Avalonia.Controls.DragDrop; +using Avalonia.Input.DragDrop; using Avalonia.Win32.Interop; -using IDataObject = Avalonia.Controls.DragDrop.IDataObject; +using IDataObject = Avalonia.Input.DragDrop.IDataObject; using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; using System.IO; using System.Runtime.Serialization.Formatters.Binary; diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs index 937471f9ad..81dd790066 100644 --- a/src/Windows/Avalonia.Win32/DragSource.cs +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; -using Avalonia.Controls.DragDrop; -using Avalonia.Controls.Platform; -using Avalonia.Interactivity; +using Avalonia.Input.DragDrop; +using Avalonia.Input.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 60527bb7d6..ee1c3046eb 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -6,13 +6,13 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization.Formatters.Binary; using System.Text; -using Avalonia.Controls.DragDrop; +using Avalonia.Input.DragDrop; using Avalonia.Win32.Interop; using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { - class OleDataObject : Avalonia.Controls.DragDrop.IDataObject + class OleDataObject : Avalonia.Input.DragDrop.IDataObject { private IDataObject _wrapped; diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 747006673a..e3791747b1 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -1,9 +1,9 @@ -using Avalonia.Controls.DragDrop; -using Avalonia.Controls.DragDrop.Raw; +using Avalonia.Input.DragDrop; +using Avalonia.Input.DragDrop.Raw; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Win32.Interop; -using IDataObject = Avalonia.Controls.DragDrop.IDataObject; +using IDataObject = Avalonia.Input.DragDrop.IDataObject; using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index ef1c4e987d..902abaf65b 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -84,8 +84,10 @@ namespace Avalonia.Win32 .Bind().ToConstant(new RenderLoop(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) - .Bind().ToConstant(s_instance) - .Bind().ToSingleton(); + .Bind().ToConstant(s_instance); + + if (OleContext.Current != null) + AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); From a0b1ad62b36e3806d1bb7da3280814dd956f0181 Mon Sep 17 00:00:00 2001 From: ahopper Date: Thu, 12 Apr 2018 11:30:53 +0100 Subject: [PATCH 27/32] Fix #1484 --- samples/ControlCatalog/ControlCatalog.csproj | 4 ++ samples/ControlCatalog/MainView.xaml | 1 + samples/ControlCatalog/Pages/PopupPage.xaml | 49 +++++++++++++++++++ .../ControlCatalog/Pages/PopupPage.xaml.cs | 19 +++++++ src/Avalonia.Controls/Primitives/Popup.cs | 47 +++++++++++++++--- 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 samples/ControlCatalog/Pages/PopupPage.xaml create mode 100644 samples/ControlCatalog/Pages/PopupPage.xaml.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 862de9d320..4fb0ff2059 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -23,6 +23,8 @@ + + @@ -84,6 +86,7 @@ Designer + Designer @@ -158,6 +161,7 @@ MenuPage.xaml + ProgressBarPage.xaml diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index a0e0df450b..3a19d8e8ed 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -27,5 +27,6 @@ + diff --git a/samples/ControlCatalog/Pages/PopupPage.xaml b/samples/ControlCatalog/Pages/PopupPage.xaml new file mode 100644 index 0000000000..8469ac480d --- /dev/null +++ b/samples/ControlCatalog/Pages/PopupPage.xaml @@ -0,0 +1,49 @@ + + + Popup + A Popup dialog + + Flyout Auto close + Flyout Toggle + + + + + Item A + Item B + Item C + Item D + Item E + Item F + Item G + + Nested Auto close + + + + Nested Content + + + + + + + + + + + Item A + Item B + Item C + Item D + Item E + Item F + Item G + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/PopupPage.xaml.cs b/samples/ControlCatalog/Pages/PopupPage.xaml.cs new file mode 100644 index 0000000000..08aca56bad --- /dev/null +++ b/samples/ControlCatalog/Pages/PopupPage.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class PopupPage : UserControl + { + public PopupPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 5cd3b22fc9..b8817d28f3 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -215,7 +215,17 @@ namespace Avalonia.Controls.Primitives { var window = _topLevel as Window; if (window != null) - window.Deactivated += WindowDeactivated; + { + window.Deactivated += WindowDeactivated; + } + else + { + var parentPopuproot = _topLevel as PopupRoot; + if(parentPopuproot != null && parentPopuproot.Parent!=null) + { + ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed; + } + } _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick); } @@ -230,7 +240,7 @@ namespace Avalonia.Controls.Primitives Opened?.Invoke(this, EventArgs.Empty); } - + /// /// Closes the popup. /// @@ -244,6 +254,14 @@ namespace Avalonia.Controls.Primitives var window = _topLevel as Window; if (window != null) window.Deactivated -= WindowDeactivated; + else + { + var parentPopuproot = _topLevel as PopupRoot; + if (parentPopuproot != null && parentPopuproot.Parent != null) + { + ((Popup)parentPopuproot.Parent).Closed -= ParentClosed; + } + } _nonClientListener?.Dispose(); _nonClientListener = null; } @@ -381,16 +399,25 @@ namespace Avalonia.Controls.Primitives { if (!StaysOpen) { - var root = ((IVisual)e.Source).GetVisualRoot(); - - if (root != this.PopupRoot) - { + if(!IsChildOrThis((IVisual)e.Source)) + { Close(); e.Handled = true; } } } + private bool IsChildOrThis(IVisual child) + { + IVisual root = child.GetVisualRoot(); + while (root is PopupRoot) + { + if (root == PopupRoot) return true; + root = ((PopupRoot)root).Parent.GetVisualRoot(); + } + return false; + } + private void WindowDeactivated(object sender, EventArgs e) { if (!StaysOpen) @@ -398,5 +425,13 @@ namespace Avalonia.Controls.Primitives Close(); } } + + private void ParentClosed(object sender, EventArgs e) + { + if (!StaysOpen) + { + Close(); + } + } } } \ No newline at end of file From c09233e4b462271ee667bd2b5312497501fdc126 Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 13 Apr 2018 13:28:49 +0200 Subject: [PATCH 28/32] renamed DragSource to InProcessDragSource --- src/Avalonia.Controls/Application.cs | 2 +- .../Platform/{DragSource.cs => InProcessDragSource.cs} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Avalonia.Controls/Platform/{DragSource.cs => InProcessDragSource.cs} (98%) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index f94a85f31d..3dc07f81e6 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -240,7 +240,7 @@ namespace Avalonia .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance) - .Bind().ToTransient(); + .Bind().ToTransient(); } } } diff --git a/src/Avalonia.Controls/Platform/DragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs similarity index 98% rename from src/Avalonia.Controls/Platform/DragSource.cs rename to src/Avalonia.Controls/Platform/InProcessDragSource.cs index ed873ec961..1e97c7bf96 100644 --- a/src/Avalonia.Controls/Platform/DragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -14,7 +14,7 @@ using Avalonia.VisualTree; namespace Avalonia.Platform { - class DragSource : IPlatformDragSource + class InProcessDragSource : IPlatformDragSource { private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton; private readonly IDragDropDevice _dragDrop; @@ -29,7 +29,7 @@ namespace Avalonia.Platform private object _originalCursor; private InputModifiers? _initialInputModifiers; - public DragSource() + public InProcessDragSource() { _inputManager = AvaloniaLocator.Current.GetService(); _dragDrop = AvaloniaLocator.Current.GetService(); From d5d8d049a415944f1ba5e851f65774d039c24448 Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 13 Apr 2018 13:30:42 +0200 Subject: [PATCH 29/32] Renamed AcceptDrag to AllowDrop and added docs --- src/Avalonia.Input/DragDrop/DragDrop.cs | 30 +++++++++++++++---- src/Avalonia.Input/DragDrop/DragDropDevice.cs | 2 +- src/Avalonia.Input/DragDrop/IDataObject.cs | 22 ++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Input/DragDrop/DragDrop.cs b/src/Avalonia.Input/DragDrop/DragDrop.cs index 9d68deb62b..0cbbfb1dbe 100644 --- a/src/Avalonia.Input/DragDrop/DragDrop.cs +++ b/src/Avalonia.Input/DragDrop/DragDrop.cs @@ -6,21 +6,39 @@ namespace Avalonia.Input.DragDrop { public static class DragDrop { + /// + /// Event which is raised, when a drag-and-drop operation enters the element. + /// public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); + /// + /// Event which is raised, when a drag-and-drop operation leaves the element. + /// public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + /// + /// Event which is raised, when a drag-and-drop operation is updated while over the element. + /// public static RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); + /// + /// Event which is raised, when a drag-and-drop operation should complete over the element. + /// public static RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); - public static AvaloniaProperty AcceptDragProperty = AvaloniaProperty.RegisterAttached("AcceptDrag", typeof(DragDrop), inherits: true); + public static AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); - public static bool GetAcceptDrag(Interactive interactive) + /// + /// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation. + /// + public static bool GetAllowDrop(Interactive interactive) { - return interactive.GetValue(AcceptDragProperty); + return interactive.GetValue(AllowDropProperty); } - - public static void SetAcceptDrag(Interactive interactive, bool value) + + /// + /// Sets a value indicating whether the given interactive can be used as the target of a drag-and-drop operation. + /// + public static void SetAllowDrop(Interactive interactive, bool value) { - interactive.SetValue(AcceptDragProperty, value); + interactive.SetValue(AllowDropProperty, value); } /// diff --git a/src/Avalonia.Input/DragDrop/DragDropDevice.cs b/src/Avalonia.Input/DragDrop/DragDropDevice.cs index 0a64800346..7ef6f212f8 100644 --- a/src/Avalonia.Input/DragDrop/DragDropDevice.cs +++ b/src/Avalonia.Input/DragDrop/DragDropDevice.cs @@ -15,7 +15,7 @@ namespace Avalonia.Input.DragDrop private Interactive GetTarget(IInputElement root, Point local) { var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); - if (target != null && DragDrop.GetAcceptDrag(target)) + if (target != null && DragDrop.GetAllowDrop(target)) return target; return null; } diff --git a/src/Avalonia.Input/DragDrop/IDataObject.cs b/src/Avalonia.Input/DragDrop/IDataObject.cs index 5d26a20cee..bf2d00f529 100644 --- a/src/Avalonia.Input/DragDrop/IDataObject.cs +++ b/src/Avalonia.Input/DragDrop/IDataObject.cs @@ -2,16 +2,38 @@ namespace Avalonia.Input.DragDrop { + /// + /// Interface to access information about the data of a drag-and-drop operation. + /// public interface IDataObject { + /// + /// Lists all formats which are present in the DataObject. + /// + /// IEnumerable GetDataFormats(); + /// + /// Checks wether a given DataFormat is present in this object + /// + /// bool Contains(string dataFormat); + /// + /// Returns the dragged text if the DataObject contains any text. + /// + /// string GetText(); + /// + /// Returns a list of filenames if the DataObject contains filenames. + /// + /// IEnumerable GetFileNames(); + /// + /// Tries to get the data of the given DataFormat. + /// object Get(string dataFormat); } } \ No newline at end of file From 36186b31078aa911ecbd3c931f1514b074d24627 Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 13 Apr 2018 13:31:53 +0200 Subject: [PATCH 30/32] added Drag+Drop sample --- samples/ControlCatalog/ControlCatalog.csproj | 8 +++ samples/ControlCatalog/MainView.xaml | 1 + .../ControlCatalog/Pages/DragAndDropPage.cs | 71 +++++++++++++++++++ .../ControlCatalog/Pages/DragAndDropPage.xaml | 19 +++++ 4 files changed, 99 insertions(+) create mode 100644 samples/ControlCatalog/Pages/DragAndDropPage.cs create mode 100644 samples/ControlCatalog/Pages/DragAndDropPage.xaml diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 862de9d320..5cdf5c59de 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -23,6 +23,8 @@ + + @@ -35,6 +37,9 @@ Designer + + Designer + Designer @@ -140,6 +145,9 @@ CheckBoxPage.xaml + + DragAndDropPage.xaml + DropDownPage.xaml diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index a0e0df450b..1107d34b3e 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -15,6 +15,7 @@ + diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.cs b/samples/ControlCatalog/Pages/DragAndDropPage.cs new file mode 100644 index 0000000000..172331204f --- /dev/null +++ b/samples/ControlCatalog/Pages/DragAndDropPage.cs @@ -0,0 +1,71 @@ +using Avalonia.Controls; +using Avalonia.Input.DragDrop; +using Avalonia.Markup.Xaml; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ControlCatalog.Pages +{ + public class DragAndDropPage : UserControl + { + private TextBlock _DropState; + private TextBlock _DragState; + private Border _DragMe; + private int DragCount = 0; + + public DragAndDropPage() + { + this.InitializeComponent(); + + _DragMe.PointerPressed += DoDrag; + + AddHandler(DragDrop.DropEvent, Drop); + AddHandler(DragDrop.DragOverEvent, DragOver); + } + + private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e) + { + DataObject dragData = new DataObject(); + dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times"); + + var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy); + switch(result) + { + case DragDropEffects.Copy: + _DragState.Text = "The text was copied"; break; + case DragDropEffects.Link: + _DragState.Text = "The text was linked"; break; + case DragDropEffects.None: + _DragState.Text = "The drag operation was canceled"; break; + } + } + + private void DragOver(object sender, DragEventArgs e) + { + // Only allow Copy or Link as Drop Operations. + e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link); + + // Only allow if the dragged data contains text or filenames. + if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames)) + e.DragEffects = DragDropEffects.None; + } + + private void Drop(object sender, DragEventArgs e) + { + if (e.Data.Contains(DataFormats.Text)) + _DropState.Text = e.Data.GetText(); + else if (e.Data.Contains(DataFormats.FileNames)) + _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames()); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + _DropState = this.Find("DropState"); + _DragState = this.Find("DragState"); + _DragMe = this.Find("DragMe"); + } + } +} diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml new file mode 100644 index 0000000000..af679d2f9a --- /dev/null +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml @@ -0,0 +1,19 @@ + + + Drag+Drop + Example of Drag+Drop capabilities + + + + Drag Me + + + Drop some text or files here + + + + \ No newline at end of file From 6bb1cb471ef37da8b756534cb241056982f42aab Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 13 Apr 2018 13:33:33 +0200 Subject: [PATCH 31/32] don't use OSX-Platform DragSource this should be used when the DragSource does work with any object. For now only strings and filenames work. --- src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index 3db02de209..ba45ad8403 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -36,7 +36,7 @@ namespace Avalonia.MonoMac .Bind().ToSingleton() .Bind().ToConstant(s_renderLoop) .Bind().ToConstant(PlatformThreadingInterface.Instance) - .Bind().ToTransient(); + /*.Bind().ToTransient()*/; } public static void Initialize() From cea86f36474d85b9cee079f477681669bbf301fe Mon Sep 17 00:00:00 2001 From: ahopper Date: Tue, 17 Apr 2018 07:08:58 +0100 Subject: [PATCH 32/32] removed popup from control catalog --- samples/ControlCatalog/ControlCatalog.csproj | 4 -- samples/ControlCatalog/MainView.xaml | 1 - samples/ControlCatalog/Pages/PopupPage.xaml | 49 ------------------- .../ControlCatalog/Pages/PopupPage.xaml.cs | 19 ------- 4 files changed, 73 deletions(-) delete mode 100644 samples/ControlCatalog/Pages/PopupPage.xaml delete mode 100644 samples/ControlCatalog/Pages/PopupPage.xaml.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 4fb0ff2059..862de9d320 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -23,8 +23,6 @@ - - @@ -86,7 +84,6 @@ Designer - Designer @@ -161,7 +158,6 @@ MenuPage.xaml - ProgressBarPage.xaml diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 3a19d8e8ed..a0e0df450b 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -27,6 +27,5 @@ - diff --git a/samples/ControlCatalog/Pages/PopupPage.xaml b/samples/ControlCatalog/Pages/PopupPage.xaml deleted file mode 100644 index 8469ac480d..0000000000 --- a/samples/ControlCatalog/Pages/PopupPage.xaml +++ /dev/null @@ -1,49 +0,0 @@ - - - Popup - A Popup dialog - - Flyout Auto close - Flyout Toggle - - - - - Item A - Item B - Item C - Item D - Item E - Item F - Item G - - Nested Auto close - - - - Nested Content - - - - - - - - - - - Item A - Item B - Item C - Item D - Item E - Item F - Item G - - - - - - - \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/PopupPage.xaml.cs b/samples/ControlCatalog/Pages/PopupPage.xaml.cs deleted file mode 100644 index 08aca56bad..0000000000 --- a/samples/ControlCatalog/Pages/PopupPage.xaml.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace ControlCatalog.Pages -{ - public class PopupPage : UserControl - { - public PopupPage() - { - this.InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - } -}