diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index 7faeb7296..326ff4ef4 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -215,6 +215,10 @@ typedef struct _GLFWlibraryNS // the callback to handle url open events GLFWhandleurlopen url_open_callback; + + // Active drag session (NSDraggingSession*) and view (NSView*) + id drag_session; + id drag_view; } _GLFWlibraryNS; // Cocoa-specific per-monitor data diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 3c4c7b8fc..bdec278cd 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -4069,7 +4069,9 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {@autore [dragItems addObject:dragItem]; } - [v beginDraggingSessionWithItems:dragItems event:event source:[v draggingSource]]; + [_glfw.ns.drag_session release]; + _glfw.ns.drag_session = [[v beginDraggingSessionWithItems:dragItems event:event source:[v draggingSource]] retain]; + _glfw.ns.drag_view = v; return 0; }} @@ -4192,10 +4194,59 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {@autore @end void -_glfwPlatformFreeDragSourceData(void) { } +_glfwPlatformFreeDragSourceData(void) { + [_glfw.ns.drag_session release]; + _glfw.ns.drag_session = nil; + _glfw.ns.drag_view = nil; +} int -_glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { (void)thumbnail; return 0; /* TODO: Implement me */ } +_glfwPlatformChangeDragImage(const GLFWimage *thumbnail) {@autoreleasepool{ + if (!_glfw.ns.drag_session || !thumbnail || !thumbnail->pixels) return 0; + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + CGFloat scaleFactor = 1.0; + if (window) { + NSWindow *nsw = window->ns.object; + scaleFactor = [nsw backingScaleFactor]; + if (scaleFactor == 0) scaleFactor = [NSScreen mainScreen].backingScaleFactor; + } + NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:thumbnail->width + pixelsHigh:thumbnail->height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:thumbnail->width * 4 + bitsPerPixel:32]; + if (!imageRep) return ENOMEM; + memcpy([imageRep bitmapData], thumbnail->pixels, thumbnail->width * thumbnail->height * 4); + NSSize pointSize = NSMakeSize(thumbnail->width / scaleFactor, thumbnail->height / scaleFactor); + [imageRep setSize:pointSize]; + NSImage* image = [[[NSImage alloc] initWithSize:pointSize] autorelease]; + if (!image) { [imageRep release]; return ENOMEM; } + [image addRepresentation:imageRep]; + [imageRep release]; + + NSArray *classes = @[[NSPasteboardItem class], [NSFilePromiseProvider class]]; + [((NSDraggingSession*)_glfw.ns.drag_session) + enumerateDraggingItemsWithOptions:0 + forView:(NSView*)_glfw.ns.drag_view + classes:classes + searchOptions:@{} + usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop) { + if (idx == 0) { + NSRect frame = [draggingItem draggingFrame]; + [draggingItem setDraggingFrame:NSMakeRect(frame.origin.x, frame.origin.y, + pointSize.width, pointSize.height) + contents:image]; + *stop = YES; + } + }]; + return 0; +}} int _glfwPlatformDragDataReady(const char *mime_type) { diff --git a/glfw/wl_window.c b/glfw/wl_window.c index b8b08be8a..006dfda12 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -3190,7 +3190,29 @@ add_drag_watch(int fd) { } int -_glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { (void)thumbnail; return 0; /* TODO: Implement me */ } +_glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { + if (!_glfw.wl.drag.drag_icon || !thumbnail || !thumbnail->pixels) return 0; + struct wl_buffer* icon_buffer = createShmBuffer(thumbnail, false, true); + if (!icon_buffer) return ENOMEM; + if (_glfw.wl.drag.drag_viewport) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + double f_scale = window ? _glfwWaylandWindowScale(window) : 1.0; + int logical_width = (int)(thumbnail->width / f_scale); + int logical_height = (int)(thumbnail->height / f_scale); + wp_viewport_set_destination(_glfw.wl.drag.drag_viewport, logical_width, logical_height); + } else { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (window) { + int scale = _glfwWaylandIntegerWindowScale(window); + wl_surface_set_buffer_scale(_glfw.wl.drag.drag_icon, scale); + } + } + wl_surface_attach(_glfw.wl.drag.drag_icon, icon_buffer, 0, 0); + wl_surface_damage(_glfw.wl.drag.drag_icon, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_commit(_glfw.wl.drag.drag_icon); + wl_buffer_destroy(icon_buffer); + return 0; +} int _glfwPlatformDragDataReady(const char *mime_type) { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 62985bee5..5d52d5e91 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -103,6 +103,7 @@ static void handle_drag_button_release(Time timestamp); static void handle_xdnd_status(const XClientMessageEvent *event); static void handle_xdnd_finished(const XClientMessageEvent *event); static bool create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y); +static bool render_drag_thumbnail_to_pixmap(const GLFWimage* thumbnail); static void handleEvents(monotonic_t timeout) { @@ -4062,6 +4063,97 @@ send_xdnd_drop(Window target, Time timestamp) { XFlush(_glfw.x11.display); } +// Render thumbnail pixels into _glfw.x11.drag.thumbnail_pixmap / thumbnail_gc. +// thumbnail_window must already exist. On success returns true. +static bool +render_drag_thumbnail_to_pixmap(const GLFWimage* thumbnail) { + // Free any previous pixmap / GC + if (_glfw.x11.drag.thumbnail_gc != None) { + XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); + _glfw.x11.drag.thumbnail_gc = None; + } + if (_glfw.x11.drag.thumbnail_pixmap != None) { + XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); + _glfw.x11.drag.thumbnail_pixmap = None; + } + + _glfw.x11.drag.thumbnail_pixmap = XCreatePixmap( + _glfw.x11.display, + _glfw.x11.drag.thumbnail_window, + thumbnail->width, + thumbnail->height, + DefaultDepth(_glfw.x11.display, _glfw.x11.screen) + ); + if (!_glfw.x11.drag.thumbnail_pixmap) return false; + + _glfw.x11.drag.thumbnail_gc = XCreateGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, 0, NULL); + if (!_glfw.x11.drag.thumbnail_gc) { + XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); + _glfw.x11.drag.thumbnail_pixmap = None; + return false; + } + + Visual* visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); + XImage* ximage = XCreateImage( + _glfw.x11.display, + visual, + DefaultDepth(_glfw.x11.display, _glfw.x11.screen), + ZPixmap, + 0, + NULL, + thumbnail->width, + thumbnail->height, + 32, + 0 + ); + if (!ximage) { + XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); + XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); + _glfw.x11.drag.thumbnail_gc = None; + _glfw.x11.drag.thumbnail_pixmap = None; + return false; + } + + int pixels_size = thumbnail->width * thumbnail->height * 4; + unsigned char* ximage_data = malloc(pixels_size); + if (!ximage_data) { + XDestroyImage(ximage); + XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); + XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); + _glfw.x11.drag.thumbnail_gc = None; + _glfw.x11.drag.thumbnail_pixmap = None; + return false; + } + ximage->data = (char*)ximage_data; + + const unsigned char* src = thumbnail->pixels; + for (int i = 0; i < thumbnail->width * thumbnail->height; i++) { + unsigned char r = src[i * 4 + 0]; + unsigned char g = src[i * 4 + 1]; + unsigned char b = src[i * 4 + 2]; + unsigned char a = src[i * 4 + 3]; + if (a < 255) { + r = (r * a) / 255; + g = (g * a) / 255; + b = (b * a) / 255; + } + unsigned long pixel; + if (ximage->byte_order == LSBFirst) + pixel = ((unsigned long)a << 24) | ((unsigned long)r << 16) | ((unsigned long)g << 8) | b; + else + pixel = ((unsigned long)b << 24) | ((unsigned long)g << 16) | ((unsigned long)r << 8) | a; + XPutPixel(ximage, i % thumbnail->width, i / thumbnail->width, pixel); + } + + XPutImage(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, _glfw.x11.drag.thumbnail_gc, + ximage, 0, 0, 0, 0, thumbnail->width, thumbnail->height); + XDestroyImage(ximage); + + XSetWindowBackgroundPixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, + _glfw.x11.drag.thumbnail_pixmap); + return true; +} + // Create thumbnail window for drag operation static bool create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y) { @@ -4091,108 +4183,12 @@ create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y) { return false; } - // Create pixmap for the thumbnail image - _glfw.x11.drag.thumbnail_pixmap = XCreatePixmap( - _glfw.x11.display, - _glfw.x11.drag.thumbnail_window, - thumbnail->width, - thumbnail->height, - DefaultDepth(_glfw.x11.display, _glfw.x11.screen) - ); - - if (!_glfw.x11.drag.thumbnail_pixmap) { + if (!render_drag_thumbnail_to_pixmap(thumbnail)) { XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); _glfw.x11.drag.thumbnail_window = None; return false; } - // Create GC for drawing - _glfw.x11.drag.thumbnail_gc = XCreateGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, 0, NULL); - if (!_glfw.x11.drag.thumbnail_gc) { - XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); - XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); - _glfw.x11.drag.thumbnail_window = None; - _glfw.x11.drag.thumbnail_pixmap = None; - return false; - } - - // Convert RGBA image data to XImage and draw to pixmap - Visual* visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); - XImage* ximage = XCreateImage( - _glfw.x11.display, - visual, - DefaultDepth(_glfw.x11.display, _glfw.x11.screen), - ZPixmap, - 0, // offset - NULL, // data (will be set below) - thumbnail->width, - thumbnail->height, - 32, // bitmap_pad - 0 // bytes_per_line (auto-calculate) - ); - - if (!ximage) { - XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); - XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); - XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); - _glfw.x11.drag.thumbnail_window = None; - _glfw.x11.drag.thumbnail_pixmap = None; - _glfw.x11.drag.thumbnail_gc = None; - return false; - } - - // Allocate buffer for XImage - int pixels_size = thumbnail->width * thumbnail->height * 4; - unsigned char* ximage_data = malloc(pixels_size); - if (!ximage_data) { - XDestroyImage(ximage); - XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); - XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); - XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); - _glfw.x11.drag.thumbnail_window = None; - _glfw.x11.drag.thumbnail_pixmap = None; - _glfw.x11.drag.thumbnail_gc = None; - return false; - } - - ximage->data = (char*)ximage_data; - - // Convert RGBA to the format expected by XImage - const unsigned char* src = thumbnail->pixels; - for (int i = 0; i < thumbnail->width * thumbnail->height; i++) { - unsigned long pixel; - unsigned char r = src[i * 4 + 0]; - unsigned char g = src[i * 4 + 1]; - unsigned char b = src[i * 4 + 2]; - unsigned char a = src[i * 4 + 3]; - - // Premultiply alpha - if (a < 255) { - r = (r * a) / 255; - g = (g * a) / 255; - b = (b * a) / 255; - } - - // Convert to X11 pixel format (typically BGRA on little-endian) - if (ximage->byte_order == LSBFirst) { - pixel = (a << 24) | (r << 16) | (g << 8) | b; - } else { - pixel = (b << 24) | (g << 16) | (r << 8) | a; - } - - XPutPixel(ximage, i % thumbnail->width, i / thumbnail->width, pixel); - } - - // Draw the image to the pixmap - XPutImage(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, _glfw.x11.drag.thumbnail_gc, - ximage, 0, 0, 0, 0, thumbnail->width, thumbnail->height); - - // Clean up XImage (including its data) - XDestroyImage(ximage); // This also frees ximage_data - - // Set the pixmap as the window's background - XSetWindowBackgroundPixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, _glfw.x11.drag.thumbnail_pixmap); - // Map the window to make it visible XMapRaised(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); XFlush(_glfw.x11.display); @@ -4475,7 +4471,32 @@ _glfwPlatformFreeDragSourceData(void) { } int -_glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { (void)thumbnail; return 0; /* TODO: Implement me */ } +_glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { + if (!_glfw.x11.drag.active) return 0; + if (!thumbnail || !thumbnail->pixels || thumbnail->width <= 0 || thumbnail->height <= 0) return 0; + + if (_glfw.x11.drag.thumbnail_window == None) { + // No thumbnail window yet; query cursor position and create one + Window root_return, child_return; + int root_x, root_y, win_x, win_y; + unsigned int mask_return; + XQueryPointer(_glfw.x11.display, _glfw.x11.root, + &root_return, &child_return, + &root_x, &root_y, &win_x, &win_y, &mask_return); + if (!create_drag_thumbnail(thumbnail, root_x + 10, root_y + 10)) return EIO; + return 0; + } + + // Resize the existing window to match the new thumbnail dimensions + XResizeWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, + thumbnail->width, thumbnail->height); + + if (!render_drag_thumbnail_to_pixmap(thumbnail)) return EIO; + + XClearWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); + XFlush(_glfw.x11.display); + return 0; +} int _glfwPlatformDragDataReady(const char *mime_type) {