mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 20:33:40 +01:00
Merge pull request #1217 from Ottermandias/dragdrop
This commit is contained in:
commit
ecc3dd7352
4 changed files with 554 additions and 0 deletions
107
Dalamud/Interface/DragDrop/DragDropInterop.cs
Normal file
107
Dalamud/Interface/DragDrop/DragDropInterop.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
// ReSharper disable UnusedMember.Local
|
||||||
|
// ReSharper disable IdentifierTypo
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
namespace Dalamud.Interface.DragDrop;
|
||||||
|
|
||||||
|
#pragma warning disable SA1600 // Elements should be documented
|
||||||
|
/// <summary> Implements interop enums and function calls to interact with external drag and drop. </summary>
|
||||||
|
internal partial class DragDropManager
|
||||||
|
{
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
[Guid("00000122-0000-0000-C000-000000000046")]
|
||||||
|
[ComImport]
|
||||||
|
public interface IDropTarget
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
|
void DragEnter([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
|
void DragOver([ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
|
void DragLeave();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
|
void Drop([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct POINTL
|
||||||
|
{
|
||||||
|
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
|
||||||
|
public int x;
|
||||||
|
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
|
||||||
|
public int y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DragDropInterop
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ModifierKeys
|
||||||
|
{
|
||||||
|
MK_NONE = 0x00,
|
||||||
|
MK_LBUTTON = 0x01,
|
||||||
|
MK_RBUTTON = 0x02,
|
||||||
|
MK_SHIFT = 0x04,
|
||||||
|
MK_CONTROL = 0x08,
|
||||||
|
MK_MBUTTON = 0x10,
|
||||||
|
MK_ALT = 0x20,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ClipboardFormat
|
||||||
|
{
|
||||||
|
CF_TEXT = 1,
|
||||||
|
CF_BITMAP = 2,
|
||||||
|
CF_DIB = 3,
|
||||||
|
CF_UNICODETEXT = 13,
|
||||||
|
CF_HDROP = 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DVAspect
|
||||||
|
{
|
||||||
|
DVASPECT_CONTENT = 0x01,
|
||||||
|
DVASPECT_THUMBNAIL = 0x02,
|
||||||
|
DVASPECT_ICON = 0x04,
|
||||||
|
DVASPECT_DOCPRINT = 0x08,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum TYMED
|
||||||
|
{
|
||||||
|
TYMED_NULL = 0x00,
|
||||||
|
TYMED_HGLOBAL = 0x01,
|
||||||
|
TYMED_FILE = 0x02,
|
||||||
|
TYMED_ISTREAM = 0x04,
|
||||||
|
TYMED_ISTORAGE = 0x08,
|
||||||
|
TYMED_GDI = 0x10,
|
||||||
|
TYMED_MFPICT = 0x20,
|
||||||
|
TYMED_ENHMF = 0x40,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DropEffects : uint
|
||||||
|
{
|
||||||
|
None = 0x00_0000_00,
|
||||||
|
Copy = 0x00_0000_01,
|
||||||
|
Move = 0x00_0000_02,
|
||||||
|
Link = 0x00_0000_04,
|
||||||
|
Scroll = 0x80_0000_00,
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("ole32.dll")]
|
||||||
|
public static extern int RegisterDragDrop(nint hwnd, IDropTarget pDropTarget);
|
||||||
|
|
||||||
|
[DllImport("ole32.dll")]
|
||||||
|
public static extern int RevokeDragDrop(nint hwnd);
|
||||||
|
|
||||||
|
[DllImport("shell32.dll")]
|
||||||
|
public static extern int DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder lpszFile, int cch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore SA1600 // Elements should be documented
|
||||||
156
Dalamud/Interface/DragDrop/DragDropManager.cs
Normal file
156
Dalamud/Interface/DragDrop/DragDropManager.cs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.DragDrop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A manager that keeps state of external windows drag and drop events,
|
||||||
|
/// and can be used to create ImGui drag and drop sources and targets for those external events.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
[ResolveVia<IDragDropManager>]
|
||||||
|
internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly InterfaceManager.InterfaceManagerWithScene interfaceManager = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
|
||||||
|
|
||||||
|
private int lastDropFrame = -2;
|
||||||
|
private int lastTooltipFrame = -1;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private DragDropManager()
|
||||||
|
=> this.Enable();
|
||||||
|
|
||||||
|
/// <summary> Gets a value indicating whether external drag and drop is available at all. </summary>
|
||||||
|
public bool ServiceAvailable { get; private set; }
|
||||||
|
|
||||||
|
/// <summary> Gets a value indicating whether a valid external drag and drop is currently active and hovering over any FFXIV-related viewport. </summary>
|
||||||
|
public bool IsDragging { get; private set; }
|
||||||
|
|
||||||
|
/// <summary> Gets a value indicating whether there are any files or directories currently being dragged, or stored from the last drop. </summary>
|
||||||
|
public bool HasPaths
|
||||||
|
=> this.Files.Count + this.Directories.Count > 0;
|
||||||
|
|
||||||
|
/// <summary> Gets the list of file paths currently being dragged from an external application over any FFXIV-related viewport, or stored from the last drop. </summary>
|
||||||
|
public IReadOnlyList<string> Files { get; private set; } = Array.Empty<string>();
|
||||||
|
|
||||||
|
/// <summary> Gets a set of all extensions available in the paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. </summary>
|
||||||
|
public IReadOnlySet<string> Extensions { get; private set; } = new HashSet<string>();
|
||||||
|
|
||||||
|
/// <summary> Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. </summary>
|
||||||
|
public IReadOnlyList<string> Directories { get; private set; } = Array.Empty<string>();
|
||||||
|
|
||||||
|
/// <summary> Enable external drag and drop. </summary>
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
if (this.ServiceAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ret = DragDropInterop.RegisterDragDrop(this.interfaceManager.Manager.WindowHandlePtr, this);
|
||||||
|
Log.Information($"[DragDrop] Registered window 0x{this.interfaceManager.Manager.WindowHandlePtr:X} for external drag and drop operations. ({ret})");
|
||||||
|
Marshal.ThrowExceptionForHR(ret);
|
||||||
|
this.ServiceAvailable = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Could not create windows drag and drop utility for window 0x{this.interfaceManager.Manager.WindowHandlePtr:X}:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Disable external drag and drop. </summary>
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
if (!this.ServiceAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ret = DragDropInterop.RevokeDragDrop(this.interfaceManager.Manager.WindowHandlePtr);
|
||||||
|
Log.Information($"[DragDrop] Disabled external drag and drop operations for window 0x{this.interfaceManager.Manager.WindowHandlePtr:X}. ({ret})");
|
||||||
|
Marshal.ThrowExceptionForHR(ret);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Could not disable windows drag and drop utility for window 0x{this.interfaceManager.Manager.WindowHandlePtr:X}:\n{ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ServiceAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Disable"/>
|
||||||
|
public void Dispose()
|
||||||
|
=> this.Disable();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDragDropManager.CreateImGuiSource(string, Func{IDragDropManager, bool}, Func{IDragDropManager, bool})"/>
|
||||||
|
public void CreateImGuiSource(string label, Func<IDragDropManager, bool> validityCheck, Func<IDragDropManager, bool> tooltipBuilder)
|
||||||
|
{
|
||||||
|
if (!this.IsDragging && !this.IsDropping())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validityCheck(this) || !ImGui.BeginDragDropSource(ImGuiDragDropFlags.SourceExtern))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetDragDropPayload(label, nint.Zero, 0);
|
||||||
|
if (this.CheckTooltipFrame(out var frame) && tooltipBuilder(this))
|
||||||
|
{
|
||||||
|
this.lastTooltipFrame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDragDropManager.CreateImGuiTarget"/>
|
||||||
|
public bool CreateImGuiTarget(string label, out IReadOnlyList<string> files, out IReadOnlyList<string> directories)
|
||||||
|
{
|
||||||
|
files = Array.Empty<string>();
|
||||||
|
directories = Array.Empty<string>();
|
||||||
|
if (!this.HasPaths || !ImGui.BeginDragDropTarget())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (ImGui.AcceptDragDropPayload(label, ImGuiDragDropFlags.AcceptBeforeDelivery).NativePtr != null && this.IsDropping())
|
||||||
|
{
|
||||||
|
this.lastDropFrame = -2;
|
||||||
|
files = this.Files;
|
||||||
|
directories = this.Directories;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckTooltipFrame(out int frame)
|
||||||
|
{
|
||||||
|
frame = ImGui.GetFrameCount();
|
||||||
|
return this.lastTooltipFrame < frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDropping()
|
||||||
|
{
|
||||||
|
var frame = ImGui.GetFrameCount();
|
||||||
|
return this.lastDropFrame == frame || this.lastDropFrame == frame - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
247
Dalamud/Interface/DragDrop/DragDropTarget.cs
Normal file
247
Dalamud/Interface/DragDrop/DragDropTarget.cs
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.DragDrop;
|
||||||
|
|
||||||
|
/// <summary> Implements the IDropTarget interface to interact with external drag and dropping. </summary>
|
||||||
|
internal partial class DragDropManager : DragDropManager.IDropTarget
|
||||||
|
{
|
||||||
|
private int lastUpdateFrame = -1;
|
||||||
|
private DragDropInterop.ModifierKeys lastKeyState = DragDropInterop.ModifierKeys.MK_NONE;
|
||||||
|
|
||||||
|
/// <summary> Create the drag and drop formats we accept. </summary>
|
||||||
|
private FORMATETC formatEtc =
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
cfFormat = (short)DragDropInterop.ClipboardFormat.CF_HDROP,
|
||||||
|
ptd = nint.Zero,
|
||||||
|
dwAspect = DVASPECT.DVASPECT_CONTENT,
|
||||||
|
lindex = -1,
|
||||||
|
tymed = TYMED.TYMED_HGLOBAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked whenever a drag and drop process drags files into any FFXIV-related viewport.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pDataObj"> The drag and drop data. </param>
|
||||||
|
/// <param name="grfKeyState"> The mouse button used to drag as well as key modifiers. </param>
|
||||||
|
/// <param name="pt"> The global cursor position. </param>
|
||||||
|
/// <param name="pdwEffect"> Effects that can be used with this drag and drop process. </param>
|
||||||
|
public void DragEnter(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect)
|
||||||
|
{
|
||||||
|
this.IsDragging = true;
|
||||||
|
this.lastKeyState = UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true);
|
||||||
|
|
||||||
|
if (pDataObj.QueryGetData(ref this.formatEtc) != 0)
|
||||||
|
{
|
||||||
|
pdwEffect = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pdwEffect &= (uint)DragDropInterop.DropEffects.Copy;
|
||||||
|
(this.Files, this.Directories) = this.GetPaths(pDataObj);
|
||||||
|
this.Extensions = this.Files.Select(Path.GetExtension).Where(p => !p.IsNullOrEmpty()).Distinct().ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("[DragDrop] Entering external Drag and Drop with {KeyState} at {PtX}, {PtY} and with {N} files.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y, this.Files.Count + this.Directories.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Invoked every windows update-frame as long as the drag and drop process keeps hovering over an FFXIV-related viewport. </summary>
|
||||||
|
/// <param name="grfKeyState"> The mouse button used to drag as well as key modifiers. </param>
|
||||||
|
/// <param name="pt"> The global cursor position. </param>
|
||||||
|
/// <param name="pdwEffect"> Effects that can be used with this drag and drop process. </param>
|
||||||
|
/// <remarks> Can be invoked more often than once a XIV frame, so we are keeping track of frames to skip unnecessary updates. </remarks>
|
||||||
|
public void DragOver(uint grfKeyState, POINTL pt, ref uint pdwEffect)
|
||||||
|
{
|
||||||
|
var frame = ImGui.GetFrameCount();
|
||||||
|
if (frame != this.lastUpdateFrame)
|
||||||
|
{
|
||||||
|
this.lastUpdateFrame = frame;
|
||||||
|
this.lastKeyState = UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false);
|
||||||
|
pdwEffect &= (uint)DragDropInterop.DropEffects.Copy;
|
||||||
|
Log.Verbose("[DragDrop] External Drag and Drop with {KeyState} at {PtX}, {PtY}.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Invoked whenever a drag and drop process that hovered over any FFXIV-related viewport leaves all FFXIV-related viewports. </summary>
|
||||||
|
public void DragLeave()
|
||||||
|
{
|
||||||
|
this.IsDragging = false;
|
||||||
|
this.Files = Array.Empty<string>();
|
||||||
|
this.Directories = Array.Empty<string>();
|
||||||
|
this.Extensions = new HashSet<string>();
|
||||||
|
Log.Debug("[DragDrop] Leaving external Drag and Drop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Invoked whenever a drag process ends by dropping over any FFXIV-related viewport. </summary>
|
||||||
|
/// <param name="pDataObj"> The drag and drop data. </param>
|
||||||
|
/// <param name="grfKeyState"> The mouse button used to drag as well as key modifiers. </param>
|
||||||
|
/// <param name="pt"> The global cursor position. </param>
|
||||||
|
/// <param name="pdwEffect"> Effects that can be used with this drag and drop process. </param>
|
||||||
|
public void Drop(IDataObject pDataObj, uint grfKeyState, POINTL pt, ref uint pdwEffect)
|
||||||
|
{
|
||||||
|
MouseDrop(this.lastKeyState);
|
||||||
|
this.lastDropFrame = ImGui.GetFrameCount();
|
||||||
|
this.IsDragging = false;
|
||||||
|
if (this.HasPaths)
|
||||||
|
{
|
||||||
|
pdwEffect &= (uint)DragDropInterop.DropEffects.Copy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pdwEffect = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("[DragDrop] Dropping {N} files with {KeyState} at {PtX}, {PtY}.", this.Files.Count + this.Directories.Count, (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DragDropInterop.ModifierKeys UpdateIo(DragDropInterop.ModifierKeys keys, bool entering)
|
||||||
|
{
|
||||||
|
var io = ImGui.GetIO();
|
||||||
|
void UpdateMouse(int mouseIdx)
|
||||||
|
{
|
||||||
|
if (entering)
|
||||||
|
{
|
||||||
|
io.MouseDownDuration[mouseIdx] = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
io.MouseDown[mouseIdx] = true;
|
||||||
|
io.AddMouseButtonEvent(mouseIdx, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_LBUTTON))
|
||||||
|
{
|
||||||
|
UpdateMouse(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_RBUTTON))
|
||||||
|
{
|
||||||
|
UpdateMouse(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_MBUTTON))
|
||||||
|
{
|
||||||
|
UpdateMouse(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_CONTROL))
|
||||||
|
{
|
||||||
|
io.KeyCtrl = true;
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftCtrl, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.KeyCtrl = false;
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftCtrl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_ALT))
|
||||||
|
{
|
||||||
|
io.KeyAlt = true;
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftAlt, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.KeyAlt = false;
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftAlt, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_SHIFT))
|
||||||
|
{
|
||||||
|
io.KeyShift = true;
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftShift, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.KeyShift = false;
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftShift, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MouseDrop(DragDropInterop.ModifierKeys keys)
|
||||||
|
{
|
||||||
|
var io = ImGui.GetIO();
|
||||||
|
void UpdateMouse(int mouseIdx)
|
||||||
|
{
|
||||||
|
io.AddMouseButtonEvent(mouseIdx, false);
|
||||||
|
io.MouseDown[mouseIdx] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_LBUTTON))
|
||||||
|
{
|
||||||
|
UpdateMouse(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_RBUTTON))
|
||||||
|
{
|
||||||
|
UpdateMouse(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.HasFlag(DragDropInterop.ModifierKeys.MK_MBUTTON))
|
||||||
|
{
|
||||||
|
UpdateMouse(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string[] Files, string[] Directories) GetPaths(IDataObject data)
|
||||||
|
{
|
||||||
|
if (!this.IsDragging)
|
||||||
|
{
|
||||||
|
return (Array.Empty<string>(), Array.Empty<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
data.GetData(ref this.formatEtc, out var stgMedium);
|
||||||
|
var numFiles = DragDropInterop.DragQueryFile(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0);
|
||||||
|
var files = new string[numFiles];
|
||||||
|
var sb = new StringBuilder(1024);
|
||||||
|
var directoryCount = 0;
|
||||||
|
var fileCount = 0;
|
||||||
|
for (var i = 0u; i < numFiles; ++i)
|
||||||
|
{
|
||||||
|
sb.Clear();
|
||||||
|
var ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity);
|
||||||
|
if (ret >= sb.Capacity)
|
||||||
|
{
|
||||||
|
sb.Capacity = ret + 1;
|
||||||
|
ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret > 0 && ret < sb.Capacity)
|
||||||
|
{
|
||||||
|
var s = sb.ToString();
|
||||||
|
if (Directory.Exists(s))
|
||||||
|
{
|
||||||
|
files[^(++directoryCount)] = s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
files[fileCount++] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileArray = fileCount > 0 ? files.Take(fileCount).ToArray() : Array.Empty<string>();
|
||||||
|
var directoryArray = directoryCount > 0 ? files.TakeLast(directoryCount).Reverse().ToArray() : Array.Empty<string>();
|
||||||
|
|
||||||
|
return (fileArray, directoryArray);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Error obtaining data from drag & drop:\n{ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Array.Empty<string>(), Array.Empty<string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Dalamud/Interface/DragDrop/IDragDropManager.cs
Normal file
44
Dalamud/Interface/DragDrop/IDragDropManager.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.DragDrop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A service to handle external drag and drop from WinAPI.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDragDropManager
|
||||||
|
{
|
||||||
|
/// <summary> Gets a value indicating whether Drag and Drop functionality is available at all. </summary>
|
||||||
|
public bool ServiceAvailable { get; }
|
||||||
|
|
||||||
|
/// <summary> Gets a value indicating whether anything is being dragged from an external application and over any of the games viewports. </summary>
|
||||||
|
public bool IsDragging { get; }
|
||||||
|
|
||||||
|
/// <summary> Gets the list of files currently being dragged from an external application over any of the games viewports. </summary>
|
||||||
|
public IReadOnlyList<string> Files { get; }
|
||||||
|
|
||||||
|
/// <summary> Gets the set of file types by extension currently being dragged from an external application over any of the games viewports. </summary>
|
||||||
|
public IReadOnlySet<string> Extensions { get; }
|
||||||
|
|
||||||
|
/// <summary> Gets the list of directories currently being dragged from an external application over any of the games viewports. </summary>
|
||||||
|
public IReadOnlyList<string> Directories { get; }
|
||||||
|
|
||||||
|
/// <summary> Create an ImGui drag & drop source that is active only if anything is being dragged from an external source. </summary>
|
||||||
|
/// <param name="label"> The label used for the drag & drop payload. </param>
|
||||||
|
/// <param name="validityCheck">A function returning whether the current status is relevant for this source. Checked before creating the source but only if something is being dragged.</param>
|
||||||
|
public void CreateImGuiSource(string label, Func<IDragDropManager, bool> validityCheck)
|
||||||
|
=> this.CreateImGuiSource(label, validityCheck, _ => false);
|
||||||
|
|
||||||
|
/// <summary> Create an ImGui drag & drop source that is active only if anything is being dragged from an external source. </summary>
|
||||||
|
/// <param name="label"> The label used for the drag & drop payload. </param>
|
||||||
|
/// <param name="validityCheck">A function returning whether the current status is relevant for this source. Checked before creating the source but only if something is being dragged.</param>
|
||||||
|
/// <param name="tooltipBuilder">Executes ImGui functions to build a tooltip. Should return true if it creates any tooltip and false otherwise. If multiple sources are active, only the first non-empty tooltip type drawn in a frame will be used.</param>
|
||||||
|
public void CreateImGuiSource(string label, Func<IDragDropManager, bool> validityCheck, Func<IDragDropManager, bool> tooltipBuilder);
|
||||||
|
|
||||||
|
/// <summary> Create an ImGui drag & drop target on the last ImGui object. </summary>
|
||||||
|
/// <param name="label">The label used for the drag & drop payload.</param>
|
||||||
|
/// <param name="files">On success, contains the list of file paths dropped onto the target.</param>
|
||||||
|
/// <param name="directories">On success, contains the list of directory paths dropped onto the target.</param>
|
||||||
|
/// <returns>True if items were dropped onto the target this frame, false otherwise.</returns>
|
||||||
|
public bool CreateImGuiTarget(string label, out IReadOnlyList<string> files, out IReadOnlyList<string> directories);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue