1
0
Эх сурвалжийг харах

Merge pull request #1417 from boombuler/DragDrop

Drag drop
Steven Kirk 7 жил өмнө
parent
commit
30c4987c3d
39 өөрчлөгдсөн 1970 нэмэгдсэн , 10 устгасан
  1. 1 0
      samples/ControlCatalog.Desktop/Program.cs
  2. 2 0
      samples/ControlCatalog.NetCore/Program.cs
  3. 0 1
      samples/ControlCatalog/ControlCatalog.csproj
  4. 1 0
      samples/ControlCatalog/MainView.xaml
  5. 71 0
      samples/ControlCatalog/Pages/DragAndDropPage.cs
  6. 19 0
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  7. 7 1
      src/Avalonia.Controls/Application.cs
  8. 212 0
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  9. 1 0
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  10. 4 1
      src/Avalonia.Input/Cursors.cs
  11. 15 0
      src/Avalonia.Input/DragDrop/DataFormats.cs
  12. 43 0
      src/Avalonia.Input/DragDrop/DataObject.cs
  13. 54 0
      src/Avalonia.Input/DragDrop/DragDrop.cs
  14. 112 0
      src/Avalonia.Input/DragDrop/DragDropDevice.cs
  15. 13 0
      src/Avalonia.Input/DragDrop/DragDropEffects.cs
  16. 18 0
      src/Avalonia.Input/DragDrop/DragEventArgs.cs
  17. 39 0
      src/Avalonia.Input/DragDrop/IDataObject.cs
  18. 8 0
      src/Avalonia.Input/DragDrop/Raw/IDragDropDevice.cs
  19. 26 0
      src/Avalonia.Input/DragDrop/Raw/RawDragEvent.cs
  20. 10 0
      src/Avalonia.Input/DragDrop/Raw/RawDragEventType.cs
  21. 14 0
      src/Avalonia.Input/Platform/IPlatformDragSource.cs
  22. 4 1
      src/Gtk/Avalonia.Gtk3/CursorFactory.cs
  23. 2 2
      src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj
  24. 4 0
      src/OSX/Avalonia.MonoMac/Cursor.cs
  25. 125 0
      src/OSX/Avalonia.MonoMac/DragSource.cs
  26. 90 0
      src/OSX/Avalonia.MonoMac/DraggingInfo.cs
  27. 2 1
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  28. 49 0
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  29. 80 0
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  30. 27 0
      src/Windows/Avalonia.Win32/CursorFactory.cs
  31. 361 0
      src/Windows/Avalonia.Win32/DataObject.cs
  32. 27 0
      src/Windows/Avalonia.Win32/DragSource.cs
  33. 99 3
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  34. 47 0
      src/Windows/Avalonia.Win32/OleContext.cs
  35. 171 0
      src/Windows/Avalonia.Win32/OleDataObject.cs
  36. 39 0
      src/Windows/Avalonia.Win32/OleDragSource.cs
  37. 161 0
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  38. 3 0
      src/Windows/Avalonia.Win32/Win32Platform.cs
  39. 9 0
      src/Windows/Avalonia.Win32/WindowImpl.cs

+ 1 - 0
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

+ 2 - 0
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'");

+ 0 - 1
samples/ControlCatalog/ControlCatalog.csproj

@@ -2,7 +2,6 @@
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>    
   </PropertyGroup>
-  
   <ItemGroup>
     <Compile Update="**\*.xaml.cs">
       <DependentUpon>%(Filename)</DependentUpon>

+ 1 - 0
samples/ControlCatalog/MainView.xaml

@@ -15,6 +15,7 @@
     <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
     <TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
     <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
+    <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
     <TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
     <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
     <TabItem Header="Image"><pages:ImagePage/></TabItem>

+ 71 - 0
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<TextBlock>("DropState");
+            _DragState = this.Find<TextBlock>("DragState");
+            _DragMe = this.Find<Border>("DragMe");
+        }
+    }
+}

+ 19 - 0
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@@ -0,0 +1,19 @@
+<UserControl xmlns="https://github.com/avaloniaui">
+    <StackPanel Orientation="Vertical" Gap="4">
+        <TextBlock Classes="h1">Drag+Drop</TextBlock>
+        <TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
+
+        <StackPanel Orientation="Horizontal"
+                Margin="0,16,0,0"
+                HorizontalAlignment="Center"
+                Gap="16">
+            <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
+                <TextBlock Name="DragState">Drag Me</TextBlock>
+            </Border>
+            <Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16" 
+                    DragDrop.AllowDrop="True">
+                <TextBlock Name="DropState">Drop some text or files here</TextBlock>
+            </Border>
+        </StackPanel>
+    </StackPanel>
+</UserControl>

+ 7 - 1
src/Avalonia.Controls/Application.cs

@@ -12,6 +12,10 @@ using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.Threading;
 using System.Reactive.Concurrency;
+using Avalonia.Input.DragDrop.Raw;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.Input.DragDrop;
 
 namespace Avalonia
 {
@@ -234,7 +238,9 @@ namespace Avalonia
                 .Bind<IStyler>().ToConstant(_styler)
                 .Bind<ILayoutManager>().ToSingleton<LayoutManager>()
                 .Bind<IApplicationLifecycle>().ToConstant(this)
-                .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance);
+                .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
+                .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
+                .Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
         }
     }
 }

+ 212 - 0
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@@ -0,0 +1,212 @@
+using System;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Threading.Tasks;
+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.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Platform
+{
+    class InProcessDragSource : IPlatformDragSource
+    {
+        private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton;
+        private readonly IDragDropDevice _dragDrop;
+        private readonly IInputManager _inputManager;
+        private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
+
+        private DragDropEffects _allowedEffects;
+        private IDataObject _draggedData;
+        private IInputElement _lastRoot;
+        private Point _lastPosition;
+        private StandardCursorType _lastCursorType;
+        private object _originalCursor;
+        private InputModifiers? _initialInputModifiers;
+
+        public InProcessDragSource()
+        {
+            _inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
+            _dragDrop = AvaloniaLocator.Current.GetService<IDragDropDevice>();
+        }
+
+        public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
+        {
+            Dispatcher.UIThread.VerifyAccess();
+            if (_draggedData == null)
+            {
+                _draggedData = data;
+                _lastRoot = null;
+                _lastPosition = default(Point);
+                _allowedEffects = allowedEffects;
+
+                using (_inputManager.PreProcess.OfType<RawMouseEventArgs>().Subscribe(ProcessMouseEvents))
+                {
+                    using (_inputManager.PreProcess.OfType<RawKeyEventArgs>().Subscribe(ProcessKeyEvents))
+                    {
+                        var effect = await _result.FirstAsync();
+                        return effect;
+                    }
+                }
+            }
+            return DragDropEffects.None;
+        }
+
+
+        private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers)
+        {
+            _lastPosition = pt;
+
+            RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects);
+            var tl = root.GetSelfAndVisualAncestors().OfType<TopLevel>().FirstOrDefault();
+            tl.PlatformImpl.Input(rawEvent);
+
+            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 StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
+        {
+            if (effects.HasFlag(DragDropEffects.Copy))
+                return StandardCursorType.DragCopy;
+            if (effects.HasFlag(DragDropEffects.Move))
+                return StandardCursorType.DragMove;
+            if (effects.HasFlag(DragDropEffects.Link))
+                return StandardCursorType.DragLink;
+            return StandardCursorType.No;
+        }
+        
+        private void UpdateCursor(IInputElement root, DragDropEffects effect)
+        {
+            if (_lastRoot != root)
+            {
+                if (_lastRoot is InputElement ieLast)
+                {
+                    if (_originalCursor == AvaloniaProperty.UnsetValue)
+                        ieLast.ClearValue(InputElement.CursorProperty);
+                    else
+                        ieLast.Cursor = _originalCursor as Cursor;
+                }
+
+                if (root is InputElement ieNew)
+                {
+                    if (!ieNew.IsSet(InputElement.CursorProperty))
+                        _originalCursor = AvaloniaProperty.UnsetValue;
+                    else
+                        _originalCursor = root.Cursor;
+                }
+                else
+                    _originalCursor = null;
+
+                _lastCursorType = StandardCursorType.Arrow;
+                _lastRoot = root;
+            }
+
+            if (root is InputElement ie)
+            {
+                var ct = GetCursorForDropEffect(effect);
+                if (ct != _lastCursorType)
+                {
+                    _lastCursorType = ct;
+                    ie.Cursor = new Cursor(ct);
+                }
+            }  
+        }
+
+        private void CancelDragging()
+        {
+            if (_lastRoot != null)
+                RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None);
+            UpdateCursor(null, DragDropEffects.None);
+            _result.OnNext(DragDropEffects.None);
+        }
+
+        private void ProcessKeyEvents(RawKeyEventArgs e)
+        {
+            if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
+            {
+                if (_lastRoot != null)
+                    RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers);
+                UpdateCursor(null, DragDropEffects.None);
+                _result.OnNext(DragDropEffects.None);
+                e.Handled = true;
+            }
+            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)
+            {
+                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;
+            }
+            
+            switch (e.Type)
+            {
+                case RawMouseEventType.LeftButtonDown:
+                case RawMouseEventType.RightButtonDown:
+                case RawMouseEventType.MiddleButtonDown:
+                case RawMouseEventType.NonClientLeftButtonDown:
+                    CancelDragging();
+                    e.Handled = true;
+                    return;
+                case RawMouseEventType.LeaveWindow:
+                    RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position,  e.InputModifiers); break;
+                case RawMouseEventType.LeftButtonUp:
+                    CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
+                case RawMouseEventType.MiddleButtonUp:
+                    CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
+                case RawMouseEventType.RightButtonUp:
+                    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)
+                            RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers);
+                        RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
+                    }
+                    else
+                        RaiseEventAndUpdateCursor(RawDragEventType.DragOver, e.Root, e.Position, e.InputModifiers);
+                    break;
+            }
+        }
+    }
+}

+ 1 - 0
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")]

+ 4 - 1
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

+ 15 - 0
src/Avalonia.Input/DragDrop/DataFormats.cs

@@ -0,0 +1,15 @@
+namespace Avalonia.Input.DragDrop
+{
+    public static class DataFormats
+    {
+        /// <summary>
+        /// Dataformat for plaintext
+        /// </summary>
+        public static string Text = nameof(Text);
+
+        /// <summary>
+        /// Dataformat for one or more filenames
+        /// </summary>
+        public static string FileNames = nameof(FileNames);
+    }
+}

+ 43 - 0
src/Avalonia.Input/DragDrop/DataObject.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Input.DragDrop
+{
+    public class DataObject : IDataObject
+    {
+        private readonly Dictionary<string, object> _items = new Dictionary<string, object>();
+
+        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<string> GetDataFormats()
+        {
+            return _items.Keys;
+        }
+
+        public IEnumerable<string> GetFileNames()
+        {
+            return Get(DataFormats.FileNames) as IEnumerable<string>;
+        }
+
+        public string GetText()
+        {
+            return Get(DataFormats.Text) as string;
+        }
+
+        public void Set(string dataFormat, object value)
+        {
+            _items[dataFormat] = value;
+        }
+    }
+}

+ 54 - 0
src/Avalonia.Input/DragDrop/DragDrop.cs

@@ -0,0 +1,54 @@
+using System.Threading.Tasks;
+using Avalonia.Interactivity;
+using Avalonia.Input.Platform;
+
+namespace Avalonia.Input.DragDrop
+{
+    public static class DragDrop
+    {
+        /// <summary>
+        /// Event which is raised, when a drag-and-drop operation enters the element.
+        /// </summary>
+        public static RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
+        /// <summary>
+        /// Event which is raised, when a drag-and-drop operation leaves the element.
+        /// </summary>
+        public static RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
+        /// <summary>
+        /// Event which is raised, when a drag-and-drop operation is updated while over the element.
+        /// </summary>
+        public static RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
+        /// <summary>
+        /// Event which is raised, when a drag-and-drop operation should complete over the element.
+        /// </summary>
+        public static RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
+
+        public static AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
+
+        /// <summary>
+        /// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation. 
+        /// </summary>
+        public static bool GetAllowDrop(Interactive interactive)
+        {
+            return interactive.GetValue(AllowDropProperty);
+        }
+
+        /// <summary>
+        /// Sets a value indicating whether the given interactive can be used as the target of a drag-and-drop operation. 
+        /// </summary>
+        public static void SetAllowDrop(Interactive interactive, bool value)
+        {
+            interactive.SetValue(AllowDropProperty, value);
+        }
+
+        /// <summary>
+        /// Starts a dragging operation with the given <see cref="IDataObject"/> and returns the applied drop effect from the target.
+        /// <seealso cref="DataObject"/>
+        /// </summary>
+        public static Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
+        {
+            var src = AvaloniaLocator.Current.GetService<IPlatformDragSource>();
+            return src?.DoDragDrop(data, allowedEffects) ?? Task.FromResult(DragDropEffects.None);
+        }
+    }
+}

+ 112 - 0
src/Avalonia.Input/DragDrop/DragDropDevice.cs

@@ -0,0 +1,112 @@
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+using System.Linq;
+using Avalonia.Input.DragDrop.Raw;
+using Avalonia.Input.Raw;
+
+namespace Avalonia.Input.DragDrop
+{
+    public class DragDropDevice : IDragDropDevice
+    {
+        public static readonly DragDropDevice Instance = new DragDropDevice();
+        
+        private Interactive _lastTarget = null;
+        
+        private Interactive GetTarget(IInputElement root, Point local)
+        {
+            var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
+            if (target != null && DragDrop.GetAllowDrop(target))
+                return target;
+            return null;
+        }
+        
+        private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
+        {
+            if (target == null)
+                return DragDropEffects.None;
+            var args = new DragEventArgs(routedEvent, data)
+            {
+                RoutedEvent = routedEvent,
+                DragEffects = operation
+            };
+            target.RaiseEvent(args);
+            return args.DragEffects;
+        }
+        
+        private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
+        {
+            _lastTarget = GetTarget(inputRoot, point);
+            return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data);
+        }
+
+        private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
+        {
+            var target = GetTarget(inputRoot, point);
+
+            if (target == _lastTarget)
+                return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data);
+            
+            try
+            {
+                if (_lastTarget != null)
+                    _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
+                return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data);
+            }
+            finally
+            {
+                _lastTarget = target;
+            }            
+        }
+
+        private void DragLeave(IInputElement inputRoot)
+        {
+            if (_lastTarget == null)
+                return;
+            try
+            {
+                _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
+            }
+            finally 
+            {
+                _lastTarget = null;
+            }
+        }
+
+        private DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
+        {
+            try
+            {
+                return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data);
+            }
+            finally 
+            {
+                _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;
+            }
+        }
+    }
+}

+ 13 - 0
src/Avalonia.Input/DragDrop/DragDropEffects.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace Avalonia.Input.DragDrop
+{
+    [Flags]
+    public enum DragDropEffects
+    {
+        None = 0,
+        Copy = 1,
+        Move = 2,
+        Link = 4,
+    }
+}

+ 18 - 0
src/Avalonia.Input/DragDrop/DragEventArgs.cs

@@ -0,0 +1,18 @@
+using Avalonia.Interactivity;
+
+namespace Avalonia.Input.DragDrop
+{
+    public class DragEventArgs : RoutedEventArgs
+    {
+        public DragDropEffects DragEffects { get; set; }
+
+        public IDataObject Data { get; private set; }
+
+        public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data)
+            : base(routedEvent)
+        {
+            this.Data = data;
+        }
+
+    }
+}

+ 39 - 0
src/Avalonia.Input/DragDrop/IDataObject.cs

@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Input.DragDrop
+{
+    /// <summary>
+    /// Interface to access information about the data of a drag-and-drop operation.
+    /// </summary>
+    public interface IDataObject
+    {
+        /// <summary>
+        /// Lists all formats which are present in the DataObject.
+        /// <seealso cref="DataFormats"/>
+        /// </summary>
+        IEnumerable<string> GetDataFormats();
+
+        /// <summary>
+        /// Checks wether a given DataFormat is present in this object
+        /// <seealso cref="DataFormats"/>
+        /// </summary>
+        bool Contains(string dataFormat);
+
+        /// <summary>
+        /// Returns the dragged text if the DataObject contains any text.
+        /// <seealso cref="DataFormats.Text"/>
+        /// </summary>
+        string GetText();
+
+        /// <summary>
+        /// Returns a list of filenames if the DataObject contains filenames.
+        /// <seealso cref="DataFormats.FileNames"/>
+        /// </summary>
+        IEnumerable<string> GetFileNames();
+        
+        /// <summary>
+        /// Tries to get the data of the given DataFormat.
+        /// </summary>
+        object Get(string dataFormat);
+    }
+}

+ 8 - 0
src/Avalonia.Input/DragDrop/Raw/IDragDropDevice.cs

@@ -0,0 +1,8 @@
+using Avalonia.Input;
+
+namespace Avalonia.Input.DragDrop.Raw
+{
+    public interface IDragDropDevice : IInputDevice
+    {
+    }
+}

+ 26 - 0
src/Avalonia.Input/DragDrop/Raw/RawDragEvent.cs

@@ -0,0 +1,26 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+
+namespace Avalonia.Input.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(IDragDropDevice inputDevice, RawDragEventType type, 
+            IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects)
+            :base(inputDevice, 0)
+        {
+            Type = type;
+            InputRoot = inputRoot;
+            Location = location;
+            Data = data;
+            Effects = effects;
+        }
+    }
+}

+ 10 - 0
src/Avalonia.Input/DragDrop/Raw/RawDragEventType.cs

@@ -0,0 +1,10 @@
+namespace Avalonia.Input.DragDrop.Raw
+{
+    public enum RawDragEventType
+    {
+        DragEnter,
+        DragOver,
+        DragLeave,
+        Drop
+    }
+}

+ 14 - 0
src/Avalonia.Input/Platform/IPlatformDragSource.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Input.DragDrop;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Input.Platform
+{
+    public interface IPlatformDragSource
+    {
+        Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects);
+    }
+}

+ 4 - 1
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<StandardCursorType, IPlatformHandle> Cache =

+ 2 - 2
src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0</TargetFrameworks>
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@@ -19,4 +19,4 @@
     <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
   </ItemGroup>
   <Import Project="..\..\..\build\MonoMac.props" />
-</Project>
+</Project>

+ 4 - 0
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,
+
             };
         }
 

+ 125 - 0
src/OSX/Avalonia.MonoMac/DragSource.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+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.Input.DragDrop;
+using Avalonia.Input.Platform;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using MonoMac;
+using MonoMac.AppKit;
+using MonoMac.CoreGraphics;
+using MonoMac.Foundation;
+using MonoMac.OpenGL;
+
+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<DragDropEffects> _result = new Subject<DragDropEffects>();
+        private readonly IInputManager _inputManager;
+        private DragDropEffects _allowedEffects;
+        
+        public override bool IgnoreModifierKeysWhileDragging => false;
+      
+        public DragSource()
+        {
+            _inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
+        }
+
+        private string DataFormatToUTI(string s)
+        {
+            if (s == DataFormats.FileNames)
+                return NSPasteboardTypeFileUrl;
+            if (s == DataFormats.Text)
+                return NSPasteboardTypeString;
+            return s;
+        }
+        
+        private NSDraggingItem CreateDraggingItem(string format, object data)
+        {
+            var pasteboardItem = new NSPasteboardItem();
+            NSData nsData;
+            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)
+                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);
+        }
+
+        public IEnumerable<NSDraggingItem> CreateDraggingItems(string format, object data)
+        {
+            if (format == DataFormats.FileNames && data is IEnumerable<string> files)
+            {
+                foreach (var file in files)
+                    yield return CreateDraggingItem(format, file);
+
+                yield break;
+            }
+
+            yield return CreateDraggingItem(format, data);
+        }
+        
+        
+        public async Task<DragDropEffects> 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<RawMouseEventArgs>().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);
+
+            _allowedEffects = allowedEffects;
+            var items = data.GetDataFormats().SelectMany(fmt => CreateDraggingItems(fmt, data.Get(fmt))).ToArray();
+            view.BeginDraggingSession(items ,ev, this);
+
+            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();
+        }
+    }
+}

+ 90 - 0
src/OSX/Avalonia.MonoMac/DraggingInfo.cs

@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Input.DragDrop;
+using MonoMac.AppKit;
+using MonoMac.Foundation;
+
+namespace Avalonia.MonoMac
+{
+    class DraggingInfo : IDataObject
+    { 
+        private readonly NSDraggingInfo _info;
+
+        public DraggingInfo(NSDraggingInfo info)
+        {
+            _info = info;
+        }
+        
+        internal static NSDragOperation ConvertDragOperation(DragDropEffects d)
+        {
+            NSDragOperation result = NSDragOperation.None;
+            if (d.HasFlag(DragDropEffects.Copy))
+                result |= NSDragOperation.Copy;
+            if (d.HasFlag(DragDropEffects.Link))
+                result |= NSDragOperation.Link;
+            if (d.HasFlag(DragDropEffects.Move))
+                result |= NSDragOperation.Move;
+            return result;
+        }
+        
+        internal static DragDropEffects ConvertDragOperation(NSDragOperation d)
+        {
+            DragDropEffects result = DragDropEffects.None;
+            if (d.HasFlag(NSDragOperation.Copy))
+                result |= DragDropEffects.Copy;
+            if (d.HasFlag(NSDragOperation.Link))
+                result |= DragDropEffects.Link;
+            if (d.HasFlag(NSDragOperation.Move))
+                result |= DragDropEffects.Move;
+            return result;
+        }
+
+        public Point Location => new Point(_info.DraggingLocation.X, _info.DraggingLocation.Y);
+        
+        public IEnumerable<string> GetDataFormats()
+        {
+            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 _info.DraggingPasteboard.GetStringForType(NSPasteboard.NSStringType);
+        }
+
+        public IEnumerable<string> GetFileNames()
+        {
+            using(var fileNames = (NSArray)_info.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType))
+            {
+                if (fileNames != null)
+                    return NSArray.StringArrayFromHandle(fileNames.Handle);
+            }
+
+            return Enumerable.Empty<string>();
+        }
+
+        public bool Contains(string dataFormat)
+        {
+            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();
+        }
+    }
+}

+ 2 - 1
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@@ -35,7 +35,8 @@ namespace Avalonia.MonoMac
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
                 .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
                 .Bind<IRenderLoop>().ToConstant(s_renderLoop)
-                .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
+                .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
+                /*.Bind<IPlatformDragSource>().ToTransient<DragSource>()*/;
         }
 
         public static void Initialize()

+ 49 - 0
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@@ -1,5 +1,7 @@
 using System;
 using System.Collections.Generic;
+using Avalonia.Input.DragDrop;
+using Avalonia.Input.DragDrop.Raw;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Platform;
@@ -18,6 +20,7 @@ namespace Avalonia.MonoMac
     {
         public TopLevelView View { get; }
         private readonly IMouseDevice _mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
+        private readonly IDragDropDevice _dragDevice = AvaloniaLocator.Current.GetService<IDragDropDevice>();
         protected TopLevelImpl()
         {
             View = new TopLevelView(this);
@@ -53,6 +56,10 @@ namespace Avalonia.MonoMac
                 _tl = tl;
                 _mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
                 _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
+                
+                RegisterForDraggedTypes(new string[] {
+                    "public.data" // register for any kind of data.
+                });
             }
 
             protected override void Dispose(bool disposing)
@@ -149,6 +156,48 @@ namespace Avalonia.MonoMac
                 UpdateCursor();
             }
 
+            private NSDragOperation SendRawDragEvent(NSDraggingInfo sender, RawDragEventType type)
+            {
+                Action<RawInputEventArgs> input = _tl.Input;
+                IDragDropDevice dragDevice = _tl._dragDevice;
+                IInputRoot root = _tl?.InputRoot;
+                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);
+                var args = new RawDragEvent(dragDevice, type, root, pt, info, dragOp);
+                input(args);
+                return DraggingInfo.ConvertDragOperation(args.Effects);
+            }
+
+            public override NSDragOperation DraggingEntered(NSDraggingInfo sender)
+            {
+                return SendRawDragEvent(sender, RawDragEventType.DragEnter);
+            }
+
+            public override NSDragOperation DraggingUpdated(NSDraggingInfo sender)
+            {
+                return SendRawDragEvent(sender, RawDragEventType.DragOver);
+            }
+
+            public override void DraggingExited(NSDraggingInfo sender)
+            {
+                SendRawDragEvent(sender, RawDragEventType.DragLeave);
+            }
+
+            public override bool PrepareForDragOperation(NSDraggingInfo sender)
+            {
+                return SendRawDragEvent(sender, RawDragEventType.DragOver) != NSDragOperation.None;
+            }
+
+            public override bool PerformDragOperation(NSDraggingInfo sender)
+            {
+                return SendRawDragEvent(sender, RawDragEventType.Drop) != NSDragOperation.None;
+            }
+            
+
             public override void SetFrameSize(CGSize newSize)
             {
                 lock (SyncRoot)

+ 80 - 0
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.Input.DragDrop;
+using Avalonia.Win32.Interop;
+
+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, params short[] synthesized)
+            {
+                Format = format;
+                Name = name;
+                Synthesized = synthesized;
+            }
+        }
+
+        private static readonly List<ClipboardFormat> FormatList = new List<ClipboardFormat>()
+        {
+            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)
+        {
+            StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH);
+            if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0)
+                return sb.ToString();
+            return null;
+        }
+
+        public static string GetFormat(short format)
+        {
+            lock (FormatList)
+            {
+                var pd = FormatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0);
+                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;
+            }
+        }
+        
+
+    }
+}

+ 27 - 0
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<StandardCursorType, int> CursorTypeMapping = new Dictionary
             <StandardCursorType, int>
         {
@@ -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<StandardCursorType, IPlatformHandle> Cache =

+ 361 - 0
src/Windows/Avalonia.Win32/DataObject.cs

@@ -0,0 +1,361 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text;
+using Avalonia.Input.DragDrop;
+using Avalonia.Win32.Interop;
+using IDataObject = Avalonia.Input.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;
+            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<string> IDataObject.GetDataFormats()
+        {
+            return _wrapped.GetDataFormats();
+        }
+
+        IEnumerable<string> 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<string> files)
+                return WriteFileListToHGlobal(ref hGlobal, files);
+            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<byte> 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<string> 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
+    }
+}

+ 27 - 0
src/Windows/Avalonia.Win32/DragSource.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Input.DragDrop;
+using Avalonia.Input.Platform;
+using Avalonia.Threading;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    class DragSource : IPlatformDragSource
+    {
+        public Task<DragDropEffects> 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]));}
+    }
+}

+ 99 - 3
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,32 @@ 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);
+
+        [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,
@@ -990,10 +1017,28 @@ namespace Avalonia.Win32.Interop
             MDT_DEFAULT = MDT_EFFECTIVE_DPI
         } 
 
-        public enum ClipboardFormat
+        public enum ClipboardFormat 
         {
+            /// <summary>
+            /// 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.
+            /// </summary>
             CF_TEXT = 1,
-            CF_UNICODETEXT = 13
+            /// <summary>
+            /// A handle to a bitmap
+            /// </summary>
+            CF_BITMAP = 2,
+            /// <summary>
+            /// A memory object containing a BITMAPINFO structure followed by the bitmap bits.
+            /// </summary>
+            CF_DIB = 3,
+            /// <summary>
+            /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.
+            /// </summary>
+            CF_UNICODETEXT = 13,
+            /// <summary>
+            /// A handle to type HDROP that identifies a list of files. 
+            /// </summary>
+            CF_HDROP = 15,
         }
 
         public struct MSG
@@ -1136,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
@@ -1300,4 +1347,53 @@ 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,
+    }
+    
+    
+    
+    [ComImport]
+    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    [Guid("00000122-0000-0000-C000-000000000046")]
+    internal interface IDropTarget
+    {
+        [PreserveSig]
+        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] 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;
+    }
 }

+ 47 - 0
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 Avalonia.Win32
+{
+    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;
+        }
+    }
+}

+ 171 - 0
src/Windows/Avalonia.Win32/OleDataObject.cs

@@ -0,0 +1,171 @@
+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.Input.DragDrop;
+using Avalonia.Win32.Interop;
+using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
+
+namespace Avalonia.Win32
+{
+    class OleDataObject : Avalonia.Input.DragDrop.IDataObject
+    {
+        private IDataObject _wrapped;
+
+        public OleDataObject(IDataObject wrapped)
+        {
+            _wrapped = wrapped;
+        }
+
+        public bool Contains(string dataFormat)
+        {
+            return GetDataFormatsCore().Any(df => StringComparer.OrdinalIgnoreCase.Equals(df, dataFormat));
+        }
+
+        public IEnumerable<string> GetDataFormats()
+        {
+            return GetDataFormatsCore().Distinct();
+        }
+
+        public string GetText()
+        {
+            return GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT) as string;
+        }
+
+        public IEnumerable<string> GetFileNames()
+        {
+            return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable<string>;
+        }
+
+        public object Get(string dataFormat)
+        {
+            return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT);
+        }
+
+        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);
+
+                        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
+                {
+                    UnmanagedMethods.ReleaseStgMedium(ref medium);
+                }
+            }
+            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<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
+        {
+            List<string> files = new List<string>();
+            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<string> 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);
+                    }
+                }
+            }
+        }
+    }
+}

+ 39 - 0
src/Windows/Avalonia.Win32/OleDragSource.cs

@@ -0,0 +1,39 @@
+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)
+        {
+            return DRAGDROP_S_USEDEFAULTCURSORS;
+        }
+    }
+}

+ 161 - 0
src/Windows/Avalonia.Win32/OleDropTarget.cs

@@ -0,0 +1,161 @@
+using Avalonia.Input.DragDrop;
+using Avalonia.Input.DragDrop.Raw;
+using Avalonia.Input;
+using Avalonia.Platform;
+using Avalonia.Win32.Interop;
+using IDataObject = Avalonia.Input.DragDrop.IDataObject;
+using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
+
+namespace Avalonia.Win32
+{
+    class OleDropTarget : IDropTarget
+    {
+        private readonly IInputElement _target;
+        private readonly ITopLevelImpl _tl;
+        private readonly IDragDropDevice _dragDevice;
+        
+        private IDataObject _currentDrag = null;
+
+        public OleDropTarget(ITopLevelImpl tl, IInputElement target)
+        {
+            _dragDevice = AvaloniaLocator.Current.GetService<IDragDropDevice>();
+            _tl = tl;
+            _target = target;
+        }
+
+        public static DropEffect ConvertDropEffect(DragDropEffects operation)
+        {
+            DropEffect result = DropEffect.None;
+            if (operation.HasFlag(DragDropEffects.Copy))
+                result |= DropEffect.Copy;
+            if (operation.HasFlag(DragDropEffects.Move))
+                result |= DropEffect.Move;
+            if (operation.HasFlag(DragDropEffects.Link))
+                result |= DropEffect.Link;
+            return result;
+        }
+
+        public static DragDropEffects ConvertDropEffect(DropEffect effect)
+        {
+            DragDropEffects result = DragDropEffects.None;
+            if (effect.HasFlag(DropEffect.Copy))
+                result |= DragDropEffects.Copy;
+            if (effect.HasFlag(DropEffect.Move))
+                result |= DragDropEffects.Move;
+            if (effect.HasFlag(DropEffect.Link))
+                result |= DragDropEffects.Link;
+            return result;
+        }
+        
+        UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect)
+        {
+            var dispatch = _tl?.Input;
+            if (dispatch == null)
+            {
+                pdwEffect = DropEffect.None;
+                return UnmanagedMethods.HRESULT.S_OK;
+            }
+            _currentDrag = pDataObj as IDataObject;
+            if (_currentDrag == null)
+                _currentDrag = new OleDataObject(pDataObj);
+            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)
+        {
+            var dispatch = _tl?.Input;
+            if (dispatch == null)
+            {
+                pdwEffect = DropEffect.None;
+                return UnmanagedMethods.HRESULT.S_OK;
+            }
+            
+            var args = new RawDragEvent(
+                _dragDevice,
+                RawDragEventType.DragOver, 
+                _target, 
+                GetDragLocation(pt), 
+                _currentDrag, 
+                ConvertDropEffect(pdwEffect)
+            );
+            dispatch(args);
+            pdwEffect = ConvertDropEffect(args.Effects);
+            
+            return UnmanagedMethods.HRESULT.S_OK;  
+        }
+
+        UnmanagedMethods.HRESULT IDropTarget.DragLeave()
+        {
+            try
+            {
+                _tl?.Input(new RawDragEvent(
+                    _dragDevice,  
+                    RawDragEventType.DragLeave, 
+                    _target, 
+                    default(Point), 
+                    null, 
+                    DragDropEffects.None
+                ));
+                return UnmanagedMethods.HRESULT.S_OK;
+            }
+            finally
+            {
+                _currentDrag = null;
+            }
+        }
+
+        UnmanagedMethods.HRESULT IDropTarget.Drop(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect)
+        {
+            try
+            {
+                var dispatch = _tl?.Input;
+                if (dispatch == null)
+                {
+                    pdwEffect = DropEffect.None;
+                    return UnmanagedMethods.HRESULT.S_OK;
+                }
+
+                _currentDrag = pDataObj as IDataObject;
+                if (_currentDrag == null)
+                    _currentDrag= new OleDataObject(pDataObj);
+                
+                var args = new RawDragEvent(
+                    _dragDevice, 
+                    RawDragEventType.Drop, 
+                    _target, 
+                    GetDragLocation(pt), 
+                    _currentDrag, 
+                    ConvertDropEffect(pdwEffect)
+                );
+                dispatch(args);
+                pdwEffect = ConvertDropEffect(args.Effects);
+            
+                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);
+        }
+    }
+}

+ 3 - 0
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -86,6 +86,9 @@ namespace Avalonia.Win32
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);
 
+            if (OleContext.Current != null)
+                AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
+
             UseDeferredRendering = deferredRendering;
             _uiThread = UnmanagedMethods.GetCurrentThreadId();
         }

+ 9 - 0
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -34,6 +34,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
@@ -310,6 +311,7 @@ namespace Avalonia.Win32
         public void SetInputRoot(IInputRoot inputRoot)
         {
             _owner = inputRoot;
+            CreateDropTarget();
         }
 
         public void SetTitle(string title)
@@ -699,6 +701,13 @@ namespace Avalonia.Win32
             }
         }
 
+        private void CreateDropTarget()
+        {
+            OleDropTarget odt = new OleDropTarget(this, _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;