Basic drag & drop

This commit is contained in:
Simon Lübeß
2025-06-17 14:43:06 +02:00
parent 2c0b000afc
commit 07faafd28d
4 changed files with 361 additions and 5 deletions
+12
View File
@@ -5,6 +5,8 @@ using GlitchyEditor.Assets;
using GlitchyEditor.Assets.Importers; using GlitchyEditor.Assets.Importers;
using GlitchyEditor.Assets.Processors; using GlitchyEditor.Assets.Processors;
using GlitchyEditor.Assets.Exporters; using GlitchyEditor.Assets.Exporters;
using DirectX.Common;
using GlitchyEditor.Platform.Windows;
namespace GlitchyEditor namespace GlitchyEditor
{ {
@@ -17,7 +19,17 @@ namespace GlitchyEditor
Log.ClientLogger = new EditorLogger(); Log.ClientLogger = new EditorLogger();
Log.EngineLogger = new EditorLogger() { IsEngineLogger = true }; Log.EngineLogger = new EditorLogger() { IsEngineLogger = true };
// TODO: Windows only
HResult result = OleInitialize(null);
Log.EngineLogger.Assert(result case .S_OK);
PushLayer(new EditorLayer(args, _contentManager)); PushLayer(new EditorLayer(args, _contentManager));
}
public ~this()
{
OleUninitialize();
} }
protected override IContentManager InitContentManager() protected override IContentManager InitContentManager()
+74 -4
View File
@@ -15,6 +15,7 @@ using System.IO;
using GlitchyEngine.Core; using GlitchyEngine.Core;
using GlitchyEditor.Assets; using GlitchyEditor.Assets;
using GlitchyEngine.Scripting; using GlitchyEngine.Scripting;
using GlitchyEditor.Platform.Windows;
namespace GlitchyEditor namespace GlitchyEditor
{ {
@@ -115,13 +116,50 @@ namespace GlitchyEditor
private bool _showImguiDemoWindow; private bool _showImguiDemoWindow;
#endif #endif
/// Registers a drag'n'drop target that will fire corresponding DragDropEvent using the engine's event system.
class GlobalDragDropTarget : IDropTargetImplBase
{
public override Result<DropEffect> OnDragEnter(int2 cursorPosition)
{
DragDropEvent event = scope DragDropEvent(.Enter, cursorPosition);
Application.Instance.OnEvent(event);
return event.OutDropEffect;
}
public override Result<DropEffect> OnDragOver(int2 cursorPosition)
{
DragDropEvent event = scope DragDropEvent(.Over, cursorPosition);
Application.Instance.OnEvent(event);
return event.OutDropEffect;
}
public override Result<void> OnDragLeave()
{
DragDropEvent event = scope DragDropEvent(.Over, .(-1, -1));
Application.Instance.OnEvent(event);
return .Ok;
}
public override Result<DropEffect> OnDrop(int2 cursorPosition)
{
DragDropEvent event = scope DragDropEvent(.Drop, cursorPosition);
Application.Instance.OnEvent(event);
return event.OutDropEffect;
}
}
private GlobalDragDropTarget _dragDropTarget ~ _?.ReleaseRef();
[AllowAppend] [AllowAppend]
public this(String[] args, EditorContentManager contentManager) : base("Editor") public this(String[] args, EditorContentManager contentManager) : base("Editor")
{ {
Application.Get().Window.IsVSync = true; Application.Instance.Window.IsVSync = true;
ScriptEngine.ApplicationInfo.IsEditor = true; ScriptEngine.ApplicationInfo.IsEditor = true;
_dragDropTarget = new .();
_dragDropTarget.Register();
_contentManager = contentManager; _contentManager = contentManager;
InitGraphics(); InitGraphics();
@@ -676,6 +714,13 @@ namespace GlitchyEditor
ImGui.TextUnformatted("Project Name:"); ImGui.TextUnformatted("Project Name:");
ImGui.InputText("##projectName", &projectNameBuffer, projectNameBuffer.Count - 1); ImGui.InputText("##projectName", &projectNameBuffer, projectNameBuffer.Count - 1);
StringView projectName = StringView(&projectNameBuffer);
if (projectName.IsWhiteSpace)
{
ImGui.TextColored(.(1, 0, 0, 1), "Project Name required!");
}
ImGui.NewLine(); ImGui.NewLine();
ImGui.TextUnformatted("Directory:"); ImGui.TextUnformatted("Directory:");
@@ -693,7 +738,6 @@ namespace GlitchyEditor
} }
} }
StringView projectName = StringView(&projectNameBuffer);
StringView directory = StringView(&projectDirectoryBuffer); StringView directory = StringView(&projectDirectoryBuffer);
String target = scope String(); String target = scope String();
@@ -703,9 +747,9 @@ namespace GlitchyEditor
ImGui.Text($"The Project will be in:\n{target}"); ImGui.Text($"The Project will be in:\n{target}");
if (Directory.Exists(target)) if (Directory.Exists(target) && !Directory.IsEmpty(target))
{ {
ImGui.TextColored(.(1, 0, 0, 1), "The directory is not empty!"); ImGui.TextColored(.(1, 1, 0, 1), "The directory is not empty!");
} }
ImGui.NewLine(); ImGui.NewLine();
@@ -1521,6 +1565,7 @@ namespace GlitchyEditor
dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e)); dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e));
dispatcher.Dispatch<KeyPressedEvent>(scope (e) => OnKeyPressed(e)); dispatcher.Dispatch<KeyPressedEvent>(scope (e) => OnKeyPressed(e));
dispatcher.Dispatch<MouseScrolledEvent>(scope (e) => OnMouseScrolled(e)); dispatcher.Dispatch<MouseScrolledEvent>(scope (e) => OnMouseScrolled(e));
dispatcher.Dispatch<DragDropEvent>(scope (e) => OnDragDrop(e));
} }
private bool OnWindowResize(WindowResizeEvent e) private bool OnWindowResize(WindowResizeEvent e)
@@ -1611,6 +1656,31 @@ namespace GlitchyEditor
return false; return false;
} }
private bool _isDraggingFromOutside;
private bool _droppedFromOutside;
private DropEffect _effectWhenDropping = .None;
private bool OnDragDrop(DragDropEvent e)
{
Log.EngineLogger.Info($"D'n'D: {e.DragDropType} {e.CursorPosition.X} {e.CursorPosition.Y}");
switch (e.DragDropType)
{
case .Enter:
_isDraggingFromOutside = true;
_droppedFromOutside = false;
case .Over:
case .Leave:
_isDraggingFromOutside = false;
case .Drop:
_droppedFromOutside = true;
}
e.OutDropEffect = _effectWhenDropping;
return true;
}
#endregion #endregion
} }
} }
@@ -0,0 +1,274 @@
using System;
using DirectX.Common;
using DirectX.Math;
using GlitchyEngine;
using GlitchyEngine.Events;
using static System.Windows;
using GlitchyEngine.Math;
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]
public static extern HResult OleInitialize(void* reserved);
[Import("Ole32.lib"), CLink]
public static extern void OleUninitialize();
[Import("Ole32.lib"), CLink]
public static extern HResult RegisterDragDrop(HWnd windowHandle, IDropTarget* dropTarget);
}
[CRepr]
struct IDropTarget : IUnknown
{
public static new Guid IID => .("00000122-0000-0000-C000-000000000046");
[CRepr]
public struct VTable : IUnknown.VTable
{
public function [CallingConvention(.Stdcall)] HResult(IDropTarget* self, /*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect) DragEnter;
public function [CallingConvention(.Stdcall)] HResult(IDropTarget* self, uint32 grfKeyState, int2 point, ref DropEffect effect) DragOver;
public function [CallingConvention(.Stdcall)] HResult(IDropTarget* self) DragLeave;
public function [CallingConvention(.Stdcall)] HResult(IDropTarget* self, /*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect) Drop;
}
public new VTable* VT => (VTable*)mVT;
public HResult DragEnter(/*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect) mut
{
return VT.DragEnter(&this, dataObject, grfKeyState, point, ref effect);
}
public HResult DragOver(uint32 grfKeyState, int2 point, ref DropEffect effect) mut
{
return VT.DragOver(&this, grfKeyState, point, ref effect);
}
public HResult DragLeave() mut
{
return VT.DragLeave(&this);
}
public HResult Drop(/*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect) mut
{
return VT.Drop(&this, dataObject, grfKeyState, point, ref effect);
}
}
[CRepr]
enum DropEffect
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
Scroll = 0x80000000,
}
abstract class IDropTargetImplBase : RefCounted
{
[CRepr]
private struct IDropTargetImpl : IDropTarget
{
public void* ClassPtr;
}
IDropTargetImpl impl;
IDropTarget.VTable vTable;
public this()
{
impl = .();
impl.[Friend]mVT = &vTable;
impl.ClassPtr = Internal.UnsafeCastToPtr(this);
vTable.QueryInterface = => QueryInterfaceImpl;
vTable.AddRef = => AddRefImpl;
vTable.Release = => ReleaseImpl;
vTable.DragEnter = => DragEnterImpl;
vTable.DragOver = => DragOverImpl;
vTable.DragLeave = => DragLeaveImpl;
vTable.Drop = => DropImpl;
}
public void Register()
{
HResult result = RegisterDragDrop(Application.Instance.Window.[Friend]_windowHandle, &impl);
Log.EngineLogger.Assert(result not case .E_OUTOFMEMORY, "Failed to register drag drop handler (E_OUTOFMEMORY). Make sure you called OleInitialize and not CoInitialize[Ex]");
Log.EngineLogger.Assert(result case .S_OK);
}
private static IDropTargetImplBase GetInstance(IUnknown* self)
{
IDropTargetImpl* impl = (.)self;
return (.)Internal.UnsafeCastToObject(impl.ClassPtr);
}
private static HResult QueryInterfaceImpl(IUnknown* self, ref Guid riid, void** output)
{
Self instance = GetInstance(self);
HResult result = .E_NOINTERFACE;
*output = null;
if (riid == IUnknown.IID || riid == IDropTarget.IID)
{
*output = (IUnknown*)&instance.impl;
instance.AddRef();
result = .S_OK;
}
return result;
}
private static uint32 AddRefImpl(IUnknown* self)
{
Self instance = GetInstance(self);
instance.AddRef();
return (uint32)instance.RefCount;
}
private static uint32 ReleaseImpl(IUnknown* self)
{
Self instance = GetInstance(self);
uint32 count = (uint32)instance.ReleaseRefNoDelete();
if (count == 0)
delete instance;
return count;
}
// TODO: Data object? KeyState?
public abstract Result<DropEffect> OnDragEnter(int2 cursorPosition);
public abstract Result<DropEffect> OnDragOver(int2 cursorPosition);
public abstract Result<void> OnDragLeave();
public abstract Result<DropEffect> OnDrop(int2 cursorPosition);
[CallingConvention(.Stdcall)]
private static HResult DragEnterImpl(IDropTarget* self, /*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect)
{
Self instance = GetInstance(self);
Result<DropEffect> result = instance.OnDragEnter(point);
if (result case .Ok(out effect))
return .S_OK;
return .E_FAIL;
}
[CallingConvention(.Stdcall)]
private static HResult DragOverImpl(IDropTarget* self, uint32 grfKeyState, int2 point, ref DropEffect effect)
{
Self instance = GetInstance(self);
Result<DropEffect> result = instance.OnDragOver(point);
if (result case .Ok(out effect))
return .S_OK;
return .E_FAIL;
}
[CallingConvention(.Stdcall)]
private static HResult DragLeaveImpl(IDropTarget* self)
{
Self instance = GetInstance(self);
Result<void> result = instance.OnDragLeave();
if (result case .Ok)
return .S_OK;
return .E_FAIL;
}
[CallingConvention(.Stdcall)]
private static HResult DropImpl(IDropTarget* self, /*IDataObject*/ IUnknown* dataObject, uint32 grfKeyState, int2 point, ref DropEffect effect)
{
Self instance = GetInstance(self);
Result<DropEffect> result = instance.OnDrop(point);
if (result case .Ok(out effect))
return .S_OK;
return .E_FAIL;
}
}
struct S
{
bool b1;
int32 i32;
bool b2;
int64 i64;
int32 i322;
}
struct SB
{
int64 i64;
int32 i32;
int32 i322;
bool b1;
bool b2;
}
struct SC
{
bool b1;
//filler 56
int32 i32;
//filler 32
bool b2;
//filler 56
int64 i64;
int32 i322;
//filler 32
}
public enum DragDropType
{
Enter,
Over,
Leave,
Drop
}
public class DragDropEvent : Event, IEvent
{
public override EventType EventType => .DragDropEvent;
public override StringView Name => "DragDropEvent";
public override EventCategory Category => .Application;
public static EventType StaticType => .DragDropEvent;
public DragDropType DragDropType { get; private set; }
public int2 CursorPosition { get; private set; }
public DropEffect OutDropEffect { get; set; }
// TODO: Keys, or perhaps just make the listeners query them...
public this(DragDropType dragDropType, int2 cursorPosition)
{
DragDropType = dragDropType;
CursorPosition = cursorPosition;
}
}
namespace GlitchyEngine.Events
{
public extension EventType
{
case DragDropEvent;
}
}