From 650aeb20c61b424f898c5de8d5431871305fa6ce Mon Sep 17 00:00:00 2001 From: Tim Miller Date: Tue, 30 Sep 2025 14:52:57 +0900 Subject: [PATCH] [OSX] Move Drag and Drop logic to TopLevelImpl (#19731) * Move Drag and Drop logic to TopLevelImpl * Update src/Avalonia.Native/TopLevelImpl.cs Co-authored-by: Max Katz --------- Co-authored-by: Max Katz Co-authored-by: Benedikt Stebner --- native/Avalonia.Native/src/OSX/TopLevelImpl.h | 3 ++ .../Avalonia.Native/src/OSX/TopLevelImpl.mm | 49 +++++++++++++++++++ .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 4 -- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 44 ----------------- .../AvaloniaNativeDragSource.cs | 2 +- src/Avalonia.Native/TopLevelImpl.cs | 19 ++++--- src/Avalonia.Native/WindowImplBase.cs | 6 --- src/Avalonia.Native/avn.idl | 5 +- 8 files changed, 68 insertions(+), 64 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/TopLevelImpl.h b/native/Avalonia.Native/src/OSX/TopLevelImpl.h index dd494ab761..b18ad21d30 100644 --- a/native/Avalonia.Native/src/OSX/TopLevelImpl.h +++ b/native/Avalonia.Native/src/OSX/TopLevelImpl.h @@ -60,6 +60,9 @@ public: virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override; virtual HRESULT GetCurrentDisplayId (CGDirectDisplayID* ret) override; + + virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, + IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) override; protected: NSCursor *cursor; virtual void UpdateAppearance(); diff --git a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm index 6200f096d3..c247e48fe0 100644 --- a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm +++ b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm @@ -6,6 +6,7 @@ #include "TopLevelImpl.h" #include "AvnTextInputMethod.h" #include "AvnView.h" +#include "common.h" TopLevelImpl::~TopLevelImpl() { View = nullptr; @@ -271,6 +272,54 @@ void TopLevelImpl::UpdateAppearance() { } +HRESULT TopLevelImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) { + START_COM_CALL; + + auto item = TryGetPasteboardItem(clipboard); + [item setString:@"" forType:GetAvnCustomDataType()]; + if (item == nil) + return E_INVALIDARG; + if (View == NULL) + return E_FAIL; + + auto nsevent = [NSApp currentEvent]; + auto nseventType = [nsevent type]; + + // If current event isn't a mouse one (probably due to malfunctioning user app) + // attempt to forge a new one + if (!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited) + || (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged))) { + // For TopLevelImpl, we don't have a Window so we use the View's window + auto window = [View window]; + if (window != nil) { + NSRect convertRect = [window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)]; + auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y); + CGPoint cgpoint = NSPointToCGPoint(nspoint); + auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft); + nsevent = [NSEvent eventWithCGEvent:cgevent]; + CFRelease(cgevent); + } + } + + auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:item]; + + auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments]; + NSRect dragItemRect = {(float) point.X, (float) point.Y, [dragItemImage size].width, [dragItemImage size].height}; + [dragItem setDraggingFrame:dragItemRect contents:dragItemImage]; + + int op = 0; + int ieffects = (int) effects; + if ((ieffects & (int) AvnDragDropEffects::Copy) != 0) + op |= NSDragOperationCopy; + if ((ieffects & (int) AvnDragDropEffects::Link) != 0) + op |= NSDragOperationLink; + if ((ieffects & (int) AvnDragDropEffects::Move) != 0) + op |= NSDragOperationMove; + [View beginDraggingSessionWithItems:@[dragItem] event:nsevent + source:CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)]; + return S_OK; +} + void TopLevelImpl::SetClientSize(NSSize size){ [View setFrameSize:size]; } diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 0c3d410094..873a520d6d 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -69,10 +69,6 @@ public: virtual HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant variant) override; - virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, - IAvnClipboard *clipboard, IAvnDndResultCallback *cb, - void *sourceHandle) override; - virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override; virtual bool IsModal(); diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 07ee404d0f..551ea4c2e0 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -411,50 +411,6 @@ HRESULT WindowBaseImpl::SetFrameThemeVariant(AvnPlatformThemeVariant variant) { return S_OK; } -HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) { - START_COM_CALL; - - auto item = TryGetPasteboardItem(clipboard); - [item setString:@"" forType:GetAvnCustomDataType()]; - if (item == nil) - return E_INVALIDARG; - if (View == NULL) - return E_FAIL; - - auto nsevent = [NSApp currentEvent]; - auto nseventType = [nsevent type]; - - // If current event isn't a mouse one (probably due to malfunctioning user app) - // attempt to forge a new one - if (!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited) - || (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged))) { - NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)]; - auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y); - CGPoint cgpoint = NSPointToCGPoint(nspoint); - auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft); - nsevent = [NSEvent eventWithCGEvent:cgevent]; - CFRelease(cgevent); - } - - auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:item]; - - auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments]; - NSRect dragItemRect = {(float) point.X, (float) point.Y, [dragItemImage size].width, [dragItemImage size].height}; - [dragItem setDraggingFrame:dragItemRect contents:dragItemImage]; - - int op = 0; - int ieffects = (int) effects; - if ((ieffects & (int) AvnDragDropEffects::Copy) != 0) - op |= NSDragOperationCopy; - if ((ieffects & (int) AvnDragDropEffects::Link) != 0) - op |= NSDragOperationLink; - if ((ieffects & (int) AvnDragDropEffects::Move) != 0) - op |= NSDragOperationMove; - [View beginDraggingSessionWithItems:@[dragItem] event:nsevent - source:CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)]; - return S_OK; -} - bool WindowBaseImpl::IsModal() { return false; } diff --git a/src/Avalonia.Native/AvaloniaNativeDragSource.cs b/src/Avalonia.Native/AvaloniaNativeDragSource.cs index 3b4fd80775..7f815f449d 100644 --- a/src/Avalonia.Native/AvaloniaNativeDragSource.cs +++ b/src/Avalonia.Native/AvaloniaNativeDragSource.cs @@ -36,7 +36,7 @@ namespace Avalonia.Native { // Sanity check var tl = TopLevel.GetTopLevel(triggerEvent.Source as Visual); - var view = tl?.PlatformImpl as WindowBaseImpl; + var view = tl?.PlatformImpl as TopLevelImpl; if (view == null) throw new ArgumentException(); diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index 32d51718d0..8b9671c967 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -52,7 +52,7 @@ internal class MacOSTopLevelHandle : IPlatformHandle, IMacOSTopLevelPlatformHand { return Native.ObtainNSViewHandleRetained(); } - + public IntPtr NSWindow => (Native as IAvnWindowBase)?.ObtainNSWindowHandle() ?? IntPtr.Zero; public IntPtr GetNSWindowRetained() @@ -98,13 +98,18 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { _handle = handle; _savedLogicalSize = ClientSize; - _savedScaling = Native?.Scaling ?? 1;; + _savedScaling = Native?.Scaling ?? 1; _nativeControlHost = new NativeControlHostImpl(Native!.CreateNativeControlHost()); _platformBehaviorInhibition = new PlatformBehaviorInhibition(Factory.CreatePlatformBehaviorInhibition()); _surfaces = new object[] { new GlPlatformSurface(Native), new MetalPlatformSurface(Native), this }; InputMethod = new AvaloniaNativeTextInputMethod(Native); } + internal void BeginDraggingSession(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard clipboard, IAvnDndResultCallback callback, IntPtr sourceHandle) + { + Native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); + } + public double DesktopScaling => 1; public IAvnTopLevel? Native => _handle?.Native; @@ -118,7 +123,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { return default; } - + var s = Native.ClientSize; return new Size(s.Width, s.Height); @@ -135,7 +140,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface public Compositor Compositor => AvaloniaNativePlatform.Compositor; public Action? Closed { get; set; } public Action? LostFocus { get; set; } - + public WindowTransparencyLevel TransparencyLevel { get => _transparencyLevel; @@ -397,7 +402,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { throw new RenderTargetNotReadyException(); } - + return new FramebufferRenderTarget(this, nativeRenderTarget); } @@ -439,7 +444,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { return; } - + var s = new Size(size->Width, size->Height); _parent._savedLogicalSize = s; _parent.Resized?.Invoke(s, (WindowResizeReason)reason); @@ -492,7 +497,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { return AvnDragDropEffects.None; } - + IDataObject? dataObject = null; if (dataObjectHandle != IntPtr.Zero) dataObject = GCHandle.FromIntPtr(dataObjectHandle).Target as IDataObject; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 9cacc25333..8aaea611ab 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -116,12 +116,6 @@ namespace Avalonia.Native Native?.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize()); } - internal void BeginDraggingSession(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard clipboard, - IAvnDndResultCallback callback, IntPtr sourceHandle) - { - Native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); - } - protected class WindowBaseEvents : TopLevelEvents, IAvnWindowBaseEvents { private readonly WindowBaseImpl _parent; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index ee9538ec0c..18a23e1af8 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -735,6 +735,9 @@ interface IAvnTopLevel : IUnknown HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode); HRESULT GetCurrentDisplayId(uint* ret); + + HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, + IAvnClipboard* clipboard, IAvnDndResultCallback* cb, [intptr]void* sourceHandle); } [uuid(e5aca675-02b7-4129-aa79-d6e417210bda), cpp-virtual-inherits] @@ -757,8 +760,6 @@ interface IAvnWindowBase : IAvnTopLevel HRESULT SetMainMenu(IAvnMenu* menu); HRESULT ObtainNSWindowHandle([intptr]void** retOut); HRESULT ObtainNSWindowHandleRetained([intptr]void** retOut); - HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, - IAvnClipboard* clipboard, IAvnDndResultCallback* cb, [intptr]void* sourceHandle); } [uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91), cpp-virtual-inherits]