committed by
GitHub
62 changed files with 2877 additions and 847 deletions
@ -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> |
||||
@ -0,0 +1,71 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,36 +0,0 @@ |
|||||
using System.Reflection; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
|
|
||||
// General Information about an assembly is controlled through the following
|
|
||||
// set of attributes. Change these attribute values to modify the information
|
|
||||
// associated with an assembly.
|
|
||||
[assembly: AssemblyTitle("ControlCatalog")] |
|
||||
[assembly: AssemblyDescription("")] |
|
||||
[assembly: AssemblyConfiguration("")] |
|
||||
[assembly: AssemblyCompany("")] |
|
||||
[assembly: AssemblyProduct("ControlCatalog")] |
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")] |
|
||||
[assembly: AssemblyTrademark("")] |
|
||||
[assembly: AssemblyCulture("")] |
|
||||
|
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||
// to COM components. If you need to access a type in this assembly from
|
|
||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||
[assembly: ComVisible(false)] |
|
||||
|
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||
[assembly: Guid("61bec86c-f307-4295-b5b8-9428610d7d55")] |
|
||||
|
|
||||
// Version information for an assembly consists of the following four values:
|
|
||||
//
|
|
||||
// Major Version
|
|
||||
// Minor Version
|
|
||||
// Build Number
|
|
||||
// Revision
|
|
||||
//
|
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||
// by using the '*' as shown below:
|
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||
[assembly: AssemblyVersion("1.0.0.0")] |
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")] |
|
||||
@ -0,0 +1,205 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using static System.Char; |
||||
|
|
||||
|
namespace Avalonia.Utilities |
||||
|
{ |
||||
|
public struct StringTokenizer : IDisposable |
||||
|
{ |
||||
|
private const char DefaultSeparatorChar = ','; |
||||
|
|
||||
|
private readonly string _s; |
||||
|
private readonly int _length; |
||||
|
private readonly char _separator; |
||||
|
private readonly string _exceptionMessage; |
||||
|
private readonly IFormatProvider _formatProvider; |
||||
|
private int _index; |
||||
|
private int _tokenIndex; |
||||
|
private int _tokenLength; |
||||
|
|
||||
|
public StringTokenizer(string s, IFormatProvider formatProvider, string exceptionMessage = null) |
||||
|
: this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage) |
||||
|
{ |
||||
|
_formatProvider = formatProvider; |
||||
|
} |
||||
|
|
||||
|
public StringTokenizer(string s, char separator = DefaultSeparatorChar, string exceptionMessage = null) |
||||
|
{ |
||||
|
_s = s ?? throw new ArgumentNullException(nameof(s)); |
||||
|
_length = s?.Length ?? 0; |
||||
|
_separator = separator; |
||||
|
_exceptionMessage = exceptionMessage; |
||||
|
_formatProvider = CultureInfo.InvariantCulture; |
||||
|
_index = 0; |
||||
|
_tokenIndex = -1; |
||||
|
_tokenLength = 0; |
||||
|
|
||||
|
while (_index < _length && IsWhiteSpace(_s, _index)) |
||||
|
{ |
||||
|
_index++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public string CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength); |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (_index != _length) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool TryReadInt32(out Int32 result, char? separator = null) |
||||
|
{ |
||||
|
var success = TryReadString(out var stringResult, separator); |
||||
|
result = success ? int.Parse(stringResult, _formatProvider) : 0; |
||||
|
return success; |
||||
|
} |
||||
|
|
||||
|
public int ReadInt32(char? separator = null) |
||||
|
{ |
||||
|
if (!TryReadInt32(out var result, separator)) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public bool TryReadDouble(out double result, char? separator = null) |
||||
|
{ |
||||
|
var success = TryReadString(out var stringResult, separator); |
||||
|
result = success ? double.Parse(stringResult, _formatProvider) : 0; |
||||
|
return success; |
||||
|
} |
||||
|
|
||||
|
public double ReadDouble(char? separator = null) |
||||
|
{ |
||||
|
if (!TryReadDouble(out var result, separator)) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public bool TryReadString(out string result, char? separator = null) |
||||
|
{ |
||||
|
var success = TryReadToken(separator ?? _separator); |
||||
|
result = CurrentToken; |
||||
|
return success; |
||||
|
} |
||||
|
|
||||
|
public string ReadString(char? separator = null) |
||||
|
{ |
||||
|
if (!TryReadString(out var result, separator)) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private bool TryReadToken(char separator) |
||||
|
{ |
||||
|
_tokenIndex = -1; |
||||
|
|
||||
|
if (_index >= _length) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var c = _s[_index]; |
||||
|
|
||||
|
var index = _index; |
||||
|
var length = 0; |
||||
|
|
||||
|
while (_index < _length) |
||||
|
{ |
||||
|
c = _s[_index]; |
||||
|
|
||||
|
if (IsWhiteSpace(c) || c == separator) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
_index++; |
||||
|
length++; |
||||
|
} |
||||
|
|
||||
|
SkipToNextToken(separator); |
||||
|
|
||||
|
_tokenIndex = index; |
||||
|
_tokenLength = length; |
||||
|
|
||||
|
if (_tokenLength < 1) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private void SkipToNextToken(char separator) |
||||
|
{ |
||||
|
if (_index < _length) |
||||
|
{ |
||||
|
var c = _s[_index]; |
||||
|
|
||||
|
if (c != separator && !IsWhiteSpace(c)) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
|
||||
|
var length = 0; |
||||
|
|
||||
|
while (_index < _length) |
||||
|
{ |
||||
|
c = _s[_index]; |
||||
|
|
||||
|
if (c == separator) |
||||
|
{ |
||||
|
length++; |
||||
|
_index++; |
||||
|
|
||||
|
if (length > 1) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (!IsWhiteSpace(c)) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
_index++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (length > 0 && _index >= _length) |
||||
|
{ |
||||
|
throw GetFormatException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private FormatException GetFormatException() => |
||||
|
_exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException(); |
||||
|
|
||||
|
private static char GetSeparatorFromFormatProvider(IFormatProvider provider) |
||||
|
{ |
||||
|
var c = DefaultSeparatorChar; |
||||
|
|
||||
|
var formatInfo = NumberFormatInfo.GetInstance(provider); |
||||
|
if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0]) |
||||
|
{ |
||||
|
c = ';'; |
||||
|
} |
||||
|
|
||||
|
return c; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,210 @@ |
|||||
|
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.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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Input.Platform; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.VisualTree; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Input.Raw; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
[Flags] |
||||
|
public enum DragDropEffects |
||||
|
{ |
||||
|
None = 0, |
||||
|
Copy = 1, |
||||
|
Move = 2, |
||||
|
Link = 4, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
/// <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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Input.Platform |
||||
|
{ |
||||
|
public interface IPlatformDragSource |
||||
|
{ |
||||
|
Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Avalonia.Input; |
||||
|
|
||||
|
namespace Avalonia.Input.Raw |
||||
|
{ |
||||
|
public interface IDragDropDevice : IInputDevice |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
|
||||
|
namespace Avalonia.Input.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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Avalonia.Input.Raw |
||||
|
{ |
||||
|
public enum RawDragEventType |
||||
|
{ |
||||
|
DragEnter, |
||||
|
DragOver, |
||||
|
DragLeave, |
||||
|
Drop |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,224 @@ |
|||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
internal static class KnownColors |
||||
|
{ |
||||
|
private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames; |
||||
|
private static readonly IReadOnlyDictionary<uint, string> _knownColors; |
||||
|
private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes; |
||||
|
|
||||
|
static KnownColors() |
||||
|
{ |
||||
|
var knownColorNames = new Dictionary<string, KnownColor>(StringComparer.OrdinalIgnoreCase); |
||||
|
var knownColors = new Dictionary<uint, string>(); |
||||
|
|
||||
|
foreach (var field in typeof(KnownColor).GetRuntimeFields()) |
||||
|
{ |
||||
|
if (field.FieldType != typeof(KnownColor)) continue; |
||||
|
var knownColor = (KnownColor)field.GetValue(null); |
||||
|
if (knownColor == KnownColor.None) continue; |
||||
|
|
||||
|
knownColorNames.Add(field.Name, knownColor); |
||||
|
|
||||
|
// some known colors have the same value, so use the first
|
||||
|
if (!knownColors.ContainsKey((uint)knownColor)) |
||||
|
{ |
||||
|
knownColors.Add((uint)knownColor, field.Name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_knownColorNames = knownColorNames; |
||||
|
_knownColors = knownColors; |
||||
|
_knownBrushes = new Dictionary<KnownColor, ISolidColorBrush>(); |
||||
|
} |
||||
|
|
||||
|
public static ISolidColorBrush GetKnownBrush(string s) |
||||
|
{ |
||||
|
var color = GetKnownColor(s); |
||||
|
return color != KnownColor.None ? color.ToBrush() : null; |
||||
|
} |
||||
|
|
||||
|
public static KnownColor GetKnownColor(string s) |
||||
|
{ |
||||
|
if (_knownColorNames.TryGetValue(s, out var color)) |
||||
|
{ |
||||
|
return color; |
||||
|
} |
||||
|
|
||||
|
return KnownColor.None; |
||||
|
} |
||||
|
|
||||
|
public static string GetKnownColorName(uint rgb) |
||||
|
{ |
||||
|
return _knownColors.TryGetValue(rgb, out var name) ? name : null; |
||||
|
} |
||||
|
|
||||
|
public static Color ToColor(this KnownColor color) |
||||
|
{ |
||||
|
return Color.FromUInt32((uint)color); |
||||
|
} |
||||
|
|
||||
|
public static ISolidColorBrush ToBrush(this KnownColor color) |
||||
|
{ |
||||
|
lock (_knownBrushes) |
||||
|
{ |
||||
|
if (!_knownBrushes.TryGetValue(color, out var brush)) |
||||
|
{ |
||||
|
brush = new Immutable.ImmutableSolidColorBrush(color.ToColor()); |
||||
|
_knownBrushes.Add(color, brush); |
||||
|
} |
||||
|
|
||||
|
return brush; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal enum KnownColor : uint |
||||
|
{ |
||||
|
None, |
||||
|
AliceBlue = 0xfff0f8ff, |
||||
|
AntiqueWhite = 0xfffaebd7, |
||||
|
Aqua = 0xff00ffff, |
||||
|
Aquamarine = 0xff7fffd4, |
||||
|
Azure = 0xfff0ffff, |
||||
|
Beige = 0xfff5f5dc, |
||||
|
Bisque = 0xffffe4c4, |
||||
|
Black = 0xff000000, |
||||
|
BlanchedAlmond = 0xffffebcd, |
||||
|
Blue = 0xff0000ff, |
||||
|
BlueViolet = 0xff8a2be2, |
||||
|
Brown = 0xffa52a2a, |
||||
|
BurlyWood = 0xffdeb887, |
||||
|
CadetBlue = 0xff5f9ea0, |
||||
|
Chartreuse = 0xff7fff00, |
||||
|
Chocolate = 0xffd2691e, |
||||
|
Coral = 0xffff7f50, |
||||
|
CornflowerBlue = 0xff6495ed, |
||||
|
Cornsilk = 0xfffff8dc, |
||||
|
Crimson = 0xffdc143c, |
||||
|
Cyan = 0xff00ffff, |
||||
|
DarkBlue = 0xff00008b, |
||||
|
DarkCyan = 0xff008b8b, |
||||
|
DarkGoldenrod = 0xffb8860b, |
||||
|
DarkGray = 0xffa9a9a9, |
||||
|
DarkGreen = 0xff006400, |
||||
|
DarkKhaki = 0xffbdb76b, |
||||
|
DarkMagenta = 0xff8b008b, |
||||
|
DarkOliveGreen = 0xff556b2f, |
||||
|
DarkOrange = 0xffff8c00, |
||||
|
DarkOrchid = 0xff9932cc, |
||||
|
DarkRed = 0xff8b0000, |
||||
|
DarkSalmon = 0xffe9967a, |
||||
|
DarkSeaGreen = 0xff8fbc8f, |
||||
|
DarkSlateBlue = 0xff483d8b, |
||||
|
DarkSlateGray = 0xff2f4f4f, |
||||
|
DarkTurquoise = 0xff00ced1, |
||||
|
DarkViolet = 0xff9400d3, |
||||
|
DeepPink = 0xffff1493, |
||||
|
DeepSkyBlue = 0xff00bfff, |
||||
|
DimGray = 0xff696969, |
||||
|
DodgerBlue = 0xff1e90ff, |
||||
|
Firebrick = 0xffb22222, |
||||
|
FloralWhite = 0xfffffaf0, |
||||
|
ForestGreen = 0xff228b22, |
||||
|
Fuchsia = 0xffff00ff, |
||||
|
Gainsboro = 0xffdcdcdc, |
||||
|
GhostWhite = 0xfff8f8ff, |
||||
|
Gold = 0xffffd700, |
||||
|
Goldenrod = 0xffdaa520, |
||||
|
Gray = 0xff808080, |
||||
|
Green = 0xff008000, |
||||
|
GreenYellow = 0xffadff2f, |
||||
|
Honeydew = 0xfff0fff0, |
||||
|
HotPink = 0xffff69b4, |
||||
|
IndianRed = 0xffcd5c5c, |
||||
|
Indigo = 0xff4b0082, |
||||
|
Ivory = 0xfffffff0, |
||||
|
Khaki = 0xfff0e68c, |
||||
|
Lavender = 0xffe6e6fa, |
||||
|
LavenderBlush = 0xfffff0f5, |
||||
|
LawnGreen = 0xff7cfc00, |
||||
|
LemonChiffon = 0xfffffacd, |
||||
|
LightBlue = 0xffadd8e6, |
||||
|
LightCoral = 0xfff08080, |
||||
|
LightCyan = 0xffe0ffff, |
||||
|
LightGoldenrodYellow = 0xfffafad2, |
||||
|
LightGreen = 0xff90ee90, |
||||
|
LightGray = 0xffd3d3d3, |
||||
|
LightPink = 0xffffb6c1, |
||||
|
LightSalmon = 0xffffa07a, |
||||
|
LightSeaGreen = 0xff20b2aa, |
||||
|
LightSkyBlue = 0xff87cefa, |
||||
|
LightSlateGray = 0xff778899, |
||||
|
LightSteelBlue = 0xffb0c4de, |
||||
|
LightYellow = 0xffffffe0, |
||||
|
Lime = 0xff00ff00, |
||||
|
LimeGreen = 0xff32cd32, |
||||
|
Linen = 0xfffaf0e6, |
||||
|
Magenta = 0xffff00ff, |
||||
|
Maroon = 0xff800000, |
||||
|
MediumAquamarine = 0xff66cdaa, |
||||
|
MediumBlue = 0xff0000cd, |
||||
|
MediumOrchid = 0xffba55d3, |
||||
|
MediumPurple = 0xff9370db, |
||||
|
MediumSeaGreen = 0xff3cb371, |
||||
|
MediumSlateBlue = 0xff7b68ee, |
||||
|
MediumSpringGreen = 0xff00fa9a, |
||||
|
MediumTurquoise = 0xff48d1cc, |
||||
|
MediumVioletRed = 0xffc71585, |
||||
|
MidnightBlue = 0xff191970, |
||||
|
MintCream = 0xfff5fffa, |
||||
|
MistyRose = 0xffffe4e1, |
||||
|
Moccasin = 0xffffe4b5, |
||||
|
NavajoWhite = 0xffffdead, |
||||
|
Navy = 0xff000080, |
||||
|
OldLace = 0xfffdf5e6, |
||||
|
Olive = 0xff808000, |
||||
|
OliveDrab = 0xff6b8e23, |
||||
|
Orange = 0xffffa500, |
||||
|
OrangeRed = 0xffff4500, |
||||
|
Orchid = 0xffda70d6, |
||||
|
PaleGoldenrod = 0xffeee8aa, |
||||
|
PaleGreen = 0xff98fb98, |
||||
|
PaleTurquoise = 0xffafeeee, |
||||
|
PaleVioletRed = 0xffdb7093, |
||||
|
PapayaWhip = 0xffffefd5, |
||||
|
PeachPuff = 0xffffdab9, |
||||
|
Peru = 0xffcd853f, |
||||
|
Pink = 0xffffc0cb, |
||||
|
Plum = 0xffdda0dd, |
||||
|
PowderBlue = 0xffb0e0e6, |
||||
|
Purple = 0xff800080, |
||||
|
Red = 0xffff0000, |
||||
|
RosyBrown = 0xffbc8f8f, |
||||
|
RoyalBlue = 0xff4169e1, |
||||
|
SaddleBrown = 0xff8b4513, |
||||
|
Salmon = 0xfffa8072, |
||||
|
SandyBrown = 0xfff4a460, |
||||
|
SeaGreen = 0xff2e8b57, |
||||
|
SeaShell = 0xfffff5ee, |
||||
|
Sienna = 0xffa0522d, |
||||
|
Silver = 0xffc0c0c0, |
||||
|
SkyBlue = 0xff87ceeb, |
||||
|
SlateBlue = 0xff6a5acd, |
||||
|
SlateGray = 0xff708090, |
||||
|
Snow = 0xfffffafa, |
||||
|
SpringGreen = 0xff00ff7f, |
||||
|
SteelBlue = 0xff4682b4, |
||||
|
Tan = 0xffd2b48c, |
||||
|
Teal = 0xff008080, |
||||
|
Thistle = 0xffd8bfd8, |
||||
|
Tomato = 0xffff6347, |
||||
|
Transparent = 0x00ffffff, |
||||
|
Turquoise = 0xff40e0d0, |
||||
|
Violet = 0xffee82ee, |
||||
|
Wheat = 0xfff5deb3, |
||||
|
White = 0xffffffff, |
||||
|
WhiteSmoke = 0xfff5f5f5, |
||||
|
Yellow = 0xffffff00, |
||||
|
YellowGreen = 0xff9acd32 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,124 @@ |
|||||
|
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; |
||||
|
using Avalonia.Input.Platform; |
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Input; |
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using Avalonia.Input; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
using Avalonia.Win32.Interop; |
||||
|
using IDataObject = Avalonia.Input.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
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Input; |
||||
|
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]));} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
using Avalonia.Win32.Interop; |
||||
|
using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; |
||||
|
|
||||
|
namespace Avalonia.Win32 |
||||
|
{ |
||||
|
class OleDataObject : Avalonia.Input.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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Win32.Interop; |
||||
|
using IDataObject = Avalonia.Input.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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue