diff --git a/GlitchyEditor/src/EditorApp.bf b/GlitchyEditor/src/EditorApp.bf index d9015f9..6a3fe77 100644 --- a/GlitchyEditor/src/EditorApp.bf +++ b/GlitchyEditor/src/EditorApp.bf @@ -9,6 +9,7 @@ using DirectX.Common; using GlitchyEditor.Platform.Windows; using GlitchyEditor.Multithreading; using System.Threading; +using GlitchyEditor.Platform; namespace GlitchyEditor { @@ -29,16 +30,14 @@ namespace GlitchyEditor _backgroundTaskManager.Init(); - // TODO: Windows only - HResult result = OleInitialize(null); - Log.EngineLogger.Assert(result case .S_OK); + DragDropManager.Init(); PushLayer(new EditorLayer(args, _contentManager)); } public ~this() { - OleUninitialize(); + DragDropManager.Deinit(); _backgroundTaskManager.Deinit(); } diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index d992846..90aaa59 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -1,4 +1,5 @@ using System; +using System.Linq; using ImGui; using GlitchyEngine; using GlitchyEditor.EditWindows; @@ -116,45 +117,6 @@ namespace GlitchyEditor private bool _showImguiDemoWindow; #endif - /// Registers a drag'n'drop target that will fire corresponding DragDropEvent using the engine's event system. - class GlobalDragDropTarget : IDropTargetImplBase - { - public override Result OnDragEnter(int2 cursorPosition) - { - DragDropEvent event = scope DragDropEvent(.Enter, cursorPosition); - Application.Instance.OnEvent(event); - return event.OutDropEffect; - } - - public override Result OnDragOver(int2 cursorPosition) - { - DragDropEvent event = scope DragDropEvent(.Over, cursorPosition); - Application.Instance.OnEvent(event); - return event.OutDropEffect; - } - - public override Result OnDragLeave() - { - DragDropEvent event = scope DragDropEvent(.Leave, .(-1, -1)); - Application.Instance.OnEvent(event); - return .Ok; - } - - public override Result OnDrop(int2 cursorPosition, List fileNames) - { - DragDropEvent event = scope DragDropEvent(.Drop, cursorPosition); - event.FileNames = fileNames; - Application.Instance.OnEvent(event); - return event.OutDropEffect; - } - } - - private GlobalDragDropTarget _dragDropTarget ~ - { - _.Unregister(); - _?.ReleaseRef(); - } - [AllowAppend] public this(String[] args, EditorContentManager contentManager) : base("Editor") { @@ -165,9 +127,6 @@ namespace GlitchyEditor // TODO: Das muss weg _layer = this; - _dragDropTarget = new .(); - _dragDropTarget.Register(); - _contentManager = contentManager; InitGraphics(); @@ -1709,7 +1668,7 @@ namespace GlitchyEditor { if (_layer._isDraggingFromOutside == .None) { - Log.EngineLogger.Warning("Setting drop effect, eventhou the user is currently not dragging."); + Log.EngineLogger.Warning("Setting drop effect, even though the user is currently not dragging."); } _layer._effectWhenDropping = effect; @@ -1722,7 +1681,7 @@ namespace GlitchyEditor ImGui.GetIO().MousePos = .(e.CursorPosition.X, e.CursorPosition.Y); } - if (e.FileNames == null) + if (e.FileNames.IsEmpty) { DeleteContainerAndItems!(_droppedFiles); _droppedFiles = null; @@ -1734,12 +1693,27 @@ namespace GlitchyEditor _droppedFiles = new List(); } - for (String fileName in e.FileNames) + int firstIndexToRemove = 0; + + for (StringView filePath in e.FileNames) { - if (!_droppedFiles.Contains(fileName, .Ordinal)) + int index = _droppedFiles.IndexOfAlt(filePath); + if (index == -1) { - _droppedFiles.Add(new String(fileName)); + index = _droppedFiles.Count; + _droppedFiles.Add(new String(filePath)); } + + // Make sure, all paths that are in e.FileNames are before firstIndexToRemove + Swap!(_droppedFiles[firstIndexToRemove], _droppedFiles[index]); + + firstIndexToRemove++; + } + + // Remove all paths beyond firstIndexToRemove + for (int i = _droppedFiles.Count; i > firstIndexToRemove; i--) + { + delete _droppedFiles.PopBack(); } } diff --git a/GlitchyEditor/src/Platform/DragDropManager.bf b/GlitchyEditor/src/Platform/DragDropManager.bf new file mode 100644 index 0000000..9fbfbf0 --- /dev/null +++ b/GlitchyEditor/src/Platform/DragDropManager.bf @@ -0,0 +1,9 @@ +namespace GlitchyEditor.Platform; + +/// Manages system drag and drop events and fires corresponding engine events. +static class DragDropManager +{ + public extern static void Init(); + + public extern static void Deinit(); +} diff --git a/GlitchyEditor/src/Platform/Windows/DropTarget.bf b/GlitchyEditor/src/Platform/Windows/DropTarget.bf index f4dd7b7..148ac02 100644 --- a/GlitchyEditor/src/Platform/Windows/DropTarget.bf +++ b/GlitchyEditor/src/Platform/Windows/DropTarget.bf @@ -1,3 +1,5 @@ +#if BF_PLATFORM_WINDOWS + using System; using DirectX.Common; using DirectX.Math; @@ -10,18 +12,18 @@ using System.IO; namespace GlitchyEditor.Platform.Windows; -// This entire thing is so platform dependent, that it really doesn't make sense to abstract while we are windows only. - static { - [Import("Ole32.lib"), CLink] + [Import("Ole32.lib"), CLink, CallingConvention(.Stdcall)] public static extern HResult OleInitialize(void* reserved); - [Import("Ole32.lib"), CLink] + [Import("Ole32.lib"), CLink, CallingConvention(.Stdcall)] public static extern void OleUninitialize(); - [Import("Ole32.lib"), CLink] + [Import("Ole32.lib"), CLink, CallingConvention(.Stdcall)] public static extern HResult RegisterDragDrop(HWnd windowHandle, IDropTarget* dropTarget); - [Import("Ole32.lib"), CLink] + [Import("Ole32.lib"), CLink, CallingConvention(.Stdcall)] public static extern HResult RevokeDragDrop(HWnd windowHandle); + [Import("ole32.dll"), CLink, CallingConvention(.Stdcall)] + public static extern HResult DoDragDrop(IDataObject* pDataObj, IDropSource* pDropSource, DropEffect dwOKEffects, out DropEffect pdwEffect); } [CRepr] @@ -85,7 +87,6 @@ public struct FORMATETC public typealias HBITMAP = int; public typealias HENHMETAFILE = int; public typealias PWSTR = char16*; - public typealias BOOL = int32; [CRepr] public struct STGMEDIUM { @@ -116,7 +117,7 @@ public struct IDataObject : IUnknown public HResult GetDataHere(ref FORMATETC pformatetc, out STGMEDIUM pmedium) mut => VT.GetDataHere(ref this, ref pformatetc, out pmedium); public HResult QueryGetData(ref FORMATETC pformatetc) mut => VT.QueryGetData(ref this, ref pformatetc); public HResult GetCanonicalFormatEtc(ref FORMATETC pformatectIn, out FORMATETC pformatetcOut) mut => VT.GetCanonicalFormatEtc(ref this, ref pformatectIn, out pformatetcOut); - public HResult SetData(ref FORMATETC pformatetc, ref STGMEDIUM pmedium, BOOL fRelease) mut => VT.SetData(ref this, ref pformatetc, ref pmedium, fRelease); + public HResult SetData(ref FORMATETC pformatetc, ref STGMEDIUM pmedium, BigBool fRelease) mut => VT.SetData(ref this, ref pformatetc, ref pmedium, fRelease); public HResult EnumFormatEtc(uint32 dwDirection, out /*IEnumFORMATETC*/ IUnknown* ppenumFormatEtc) mut => VT.EnumFormatEtc(ref this, dwDirection, out ppenumFormatEtc); public HResult DAdvise(ref FORMATETC pformatetc, uint32 advf, ref /*IAdviseSink*/ IUnknown* pAdvSink, out uint32 pdwConnection) mut => VT.DAdvise(ref this, ref pformatetc, advf, ref pAdvSink, out pdwConnection); public HResult DUnadvise(uint32 dwConnection) mut => VT.DUnadvise(ref this, dwConnection); @@ -129,7 +130,7 @@ public struct IDataObject : IUnknown public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, ref FORMATETC pformatetc, out STGMEDIUM pmedium) GetDataHere; public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, ref FORMATETC pformatetc) QueryGetData; public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, ref FORMATETC pformatectIn, out FORMATETC pformatetcOut) GetCanonicalFormatEtc; - public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, ref FORMATETC pformatetc, ref STGMEDIUM pmedium, BOOL fRelease) SetData; + public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, ref FORMATETC pformatetc, ref STGMEDIUM pmedium, BigBool fRelease) SetData; public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, uint32 dwDirection, out /*IEnumFORMATETC*/ IUnknown* ppenumFormatEtc) EnumFormatEtc; public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, ref FORMATETC pformatetc, uint32 advf, ref /*IAdviseSink*/ IUnknown* pAdvSink, out uint32 pdwConnection) DAdvise; public new function [CallingConvention(.Stdcall)] HResult(ref IDataObject self, uint32 dwConnection) DUnadvise; @@ -229,6 +230,8 @@ abstract class IDropTargetImplBase : RefCounted { HResult result = RevokeDragDrop(Application.Instance.Window.[Friend]_windowHandle); Log.EngineLogger.Assert(result case .S_OK); + + OleUninitialize(); } private static IDropTargetImplBase GetInstance(IUnknown* self) @@ -279,7 +282,7 @@ abstract class IDropTargetImplBase : RefCounted public abstract Result OnDragEnter(int2 cursorPosition); public abstract Result OnDragOver(int2 cursorPosition); public abstract Result OnDragLeave(); - public abstract Result OnDrop(int2 cursorPosition, List fileNames); + public abstract Result OnDrop(int2 cursorPosition, Span fileNames); [CallingConvention(.Stdcall)] private static HResult DragEnterImpl(IDropTarget* self, /*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect) @@ -338,15 +341,16 @@ abstract class IDropTargetImplBase : RefCounted tymed = (.)TYMED.HGLOBAL }; - List files = null; - defer {DeleteContainerAndItems!(files);} + String paths = scope .(); + List files = null; + defer { delete files; } if (dataObject.GetData(ref format, var stgm).Succeeded) { HDROP hdrop = (HDROP)stgm.hGlobal; uint32 fileCount = DragQueryFileW(hdrop, 0xFFFFFFFF, null, 0); - files = new List(fileCount); + files = new List(fileCount); List buffer = scope List(); buffer.Resize(256); @@ -359,14 +363,27 @@ abstract class IDropTargetImplBase : RefCounted uint32 retrievedSize = DragQueryFileW(hdrop, i, buffer.Ptr, (.)buffer.Count); if (retrievedSize > 0 && retrievedSize < buffer.Count) { - String str = new String(buffer.Ptr); - files.Add(str); + int startIndex = paths.Length; + paths.Append(buffer); + int endIndex = paths.Length; + + // StringViews might be invalid until fixup step down below + files.Add(StringView(paths, startIndex, endIndex - startIndex)); } } ReleaseStgMedium(ref stgm); } + int index = 0; + + // Fixup pointer of string views + for (ref StringView path in ref files) + { + path.Ptr = paths.Ptr + index; + index += path.Length; + } + Result result = instance.OnDrop(point, files); if (result case .Ok(out effect)) @@ -430,8 +447,8 @@ public class DragDropEvent : Event, IEvent public DropEffect OutDropEffect { get; set; } - // Do not keep references to this list or any of it's items outside of the event handler, they will not survive it. - public List FileNames { get; set; } + /// Do not keep references to this list or any of it's items outside of the event handler, they will not survive it. + public Span FileNames { get; set; } // TODO: Keys, or perhaps just make the listeners query them... @@ -449,3 +466,5 @@ namespace GlitchyEngine.Events case DragDropEvent; } } + +#endif diff --git a/GlitchyEditor/src/Platform/Windows/WindowsDragDropManager.bf b/GlitchyEditor/src/Platform/Windows/WindowsDragDropManager.bf new file mode 100644 index 0000000..0df0134 --- /dev/null +++ b/GlitchyEditor/src/Platform/Windows/WindowsDragDropManager.bf @@ -0,0 +1,79 @@ +#if BF_PLATFORM_WINDOWS + +using System; +using System.Collections; +using GlitchyEngine; +using GlitchyEngine.Math; +using GlitchyEditor.Platform.Windows; +using DirectX.Common; + +namespace GlitchyEditor.Platform; + +extension DragDropManager +{ + private static DragDropEventTranslator _dragDropTarget; + + public override static void Init() + { + HResult result = OleInitialize(null); + Log.EngineLogger.Assert(result case .S_OK); + + _dragDropTarget = new .(); + _dragDropTarget.Register(); + } + + public override static void Deinit() + { + _dragDropTarget.Unregister(); + ReleaseRefAndNullify!(_dragDropTarget); + } + + private static bool _isDragging; + + public static void StartDragDrop() + { + if (_isDragging) + return; + + _isDragging = true; + + IDataObject* dataObject = null; + + //HResult result = DoDragDrop(ref dataObject); + } +} + +/// Registers a Windows drag'n'drop target that will fire Windows drop related events using the engine's event system. +class DragDropEventTranslator : IDropTargetImplBase +{ + public override Result OnDragEnter(int2 cursorPosition) + { + DragDropEvent event = scope DragDropEvent(.Enter, cursorPosition); + Application.Instance.OnEvent(event); + return event.OutDropEffect; + } + + public override Result OnDragOver(int2 cursorPosition) + { + DragDropEvent event = scope DragDropEvent(.Over, cursorPosition); + Application.Instance.OnEvent(event); + return event.OutDropEffect; + } + + public override Result OnDragLeave() + { + DragDropEvent event = scope DragDropEvent(.Leave, .(-1, -1)); + Application.Instance.OnEvent(event); + return .Ok; + } + + public override Result OnDrop(int2 cursorPosition, Span fileNames) + { + DragDropEvent event = scope DragDropEvent(.Drop, cursorPosition); + event.FileNames = fileNames; + Application.Instance.OnEvent(event); + return event.OutDropEffect; + } +} + +#endif diff --git a/GlitchyEngine/src/Helper.bf b/GlitchyEngine/src/Helper.bf index 690d7f7..83412b9 100644 --- a/GlitchyEngine/src/Helper.bf +++ b/GlitchyEngine/src/Helper.bf @@ -31,6 +31,13 @@ namespace GlitchyEngine value?.ReleaseRef(); value = null; } + + /// Releases the reference to value and nullifies it. + public static mixin ReleaseRefAndNullify(T value) where T: RefCounted + { + value?.ReleaseRef(); + value = null; + } public static mixin DeleteContainerAndReleaseItems(var container) {