From b20263cf9b6d85c7e5919497b5d36b4c81463b15 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 13:00:53 +0300 Subject: feat: implement X11 and Cocoa drop-targets, popups, and selection gaps Resolve gaps identified in yasl-gaps-report.md: - X11: - Add XDND v5 file drop support and text/uri-list parsing (YASL_INPUT_DROP_FILES) - Dispatch surface drop-target events (YASL_DRAG_OVER, YASL_DRAG_LEAVE, YASL_DRAG_DROP, YASL_DROP_OVER, YASL_DROP_LEAVE) - Implement override-redirect popups (surface_create_popup, surface_popup_reposition) - Support drag images with override-redirect icon window - Set YASL_CAP_POPUPS capability - Handle XIHierarchyChanged reset cleanly - Cocoa: - Dispatch surface drop-target events for internal drags and external NSDraggingDestination - Implement child window popups (cocoa_surface_create_popup, cocoa_surface_popup_reposition) - Implement primary selection via dedicated pasteboard - Support drag images with floating borderless NSWindow - Track pointer buttons and push YASL_INPUT_RESET on focus loss - Set YASL_CAP_POPUPS and YASL_CAP_PRIMARY_SELECTION capabilities - Tests: - Add comprehensive unit test suite in tests/x11/test_tablet.c --- src/cocoa/window.m | 519 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 483 insertions(+), 36 deletions(-) (limited to 'src/cocoa/window.m') diff --git a/src/cocoa/window.m b/src/cocoa/window.m index 7b59865..78ab0bd 100644 --- a/src/cocoa/window.m +++ b/src/cocoa/window.m @@ -55,6 +55,10 @@ struct cocoa_surface { id software_queue; CADisplayLink *link; + struct cocoa_surface *parent; + bool is_popup; + struct yasl_vector2i content_offset; + const struct yasl_surface_cbs *cbs; void *ud; uint64_t frames_skipped; @@ -91,6 +95,7 @@ struct cocoa_surface { double scroll_x; uint32_t state; uint32_t mods; + uint32_t buttons; enum yasl_cursor cursor; enum yasl_pointer_device tablet_device; bool tablet_down; @@ -118,8 +123,14 @@ struct cocoa_display { struct cocoa_surface *origin; struct cocoa_surface *carried; struct yasl_vector2i carry_offset; + struct cocoa_surface *hover; + struct yasl_vector2i hover_pos; struct yasl_drag_listener listener; void *ud; + NSImage *image; + NSWindow *image_window; + struct yasl_vector2i image_size; + struct yasl_vector2i image_hotspot; } drag; int depth; bool open; @@ -135,9 +146,29 @@ static void drag_end(struct cocoa_display *d, enum yasl_drag_end how) if (!d || !d->drag.active) return; struct yasl_drag_listener l = d->drag.listener; void *ud = d->drag.ud; + struct cocoa_surface *hover = d->drag.hover; + struct yasl_vector2i pos = d->drag.hover_pos; + d->drag.active = false; + d->drag.listener = (struct yasl_drag_listener){0}; + d->drag.hover = NULL; d->drag.origin = NULL; d->drag.carried = NULL; + + if (d->drag.image_window) { + [d->drag.image_window close]; + [d->drag.image_window release]; + d->drag.image_window = nil; + } + [d->drag.image release]; + d->drag.image = nil; + + if (hover && !hover->destroyed && hover->cbs && hover->cbs->drag) { + if (how == YASL_DRAG_DROPPED) + hover->cbs->drag(&hover->base, YASL_DRAG_DROP, pos, hover->ud); + else + hover->cbs->drag(&hover->base, YASL_DRAG_LEAVE, pos, hover->ud); + } if (l.end) l.end(ud, how); } @@ -159,10 +190,26 @@ static void set_state(struct cocoa_surface *w, uint32_t bit, bool on) darwin_ctx.depth--; } +static void check_pointer_reset(struct cocoa_surface *w) +{ + if (!w || w->closed) return; + if (w->buttons != 0 || w->tablet_down) { + struct yasl_input_event ev = { + .kind = YASL_INPUT_RESET, + .surface = &w->base, + }; + input_queue_push(&w->input, &ev); + w->buttons = 0; + w->tablet_down = false; + } +} + static void push_focus(struct cocoa_surface *w, bool focused) { if (!w || w->closed) return; + if (!focused) + check_pointer_reset(w); struct yasl_input_event ev = { .kind = YASL_INPUT_FOCUS, .surface = &w->base, @@ -303,6 +350,76 @@ static struct yasl_vector2i event_pos(struct cocoa_surface *w, NSEvent *ev) }; } +static void update_drag_hover(struct cocoa_display *d, NSPoint screenPoint) +{ + if (!d || !d->drag.active) return; + + NSInteger excludeNum = 0; + if (d->drag.carried && d->drag.carried->window) + excludeNum = d->drag.carried->window.windowNumber; + else if (d->drag.image_window) + excludeNum = d->drag.image_window.windowNumber; + + NSInteger winNum = [NSWindow windowNumberAtPoint:screenPoint belowWindowWithWindowNumber:excludeNum]; + struct cocoa_surface *target = NULL; + struct yasl_vector2i pos = {0}; + + if (winNum > 0) { + NSWindow *nswin = [NSApp windowWithWindowNumber:winNum]; + if (nswin && [nswin isKindOfClass:[YaslWindow class]]) { + struct cocoa_surface *candidate = ((YaslWindow *)nswin).surface; + if (candidate && candidate != d->drag.carried && !candidate->closed && candidate->view) { + NSPoint winPoint = [candidate->window convertPointFromScreen:screenPoint]; + NSPoint viewPoint = [candidate->view convertPoint:winPoint fromView:nil]; + if (NSPointInRect(viewPoint, candidate->view.bounds)) { + CGFloat scale = candidate->window.backingScaleFactor; + if (scale < 1.0) scale = 1.0; + pos.x = (int)floor(viewPoint.x * scale); + pos.y = (int)floor((candidate->view.bounds.size.height - viewPoint.y) * scale); + target = candidate; + } + } + } + if (!target) { + for (int i = 0; i < d->count; i++) { + struct cocoa_surface *s = d->windows[i]; + if (s != d->drag.carried && !s->closed && s->window && s->window.windowNumber == winNum && s->view) { + NSPoint winPoint = [s->window convertPointFromScreen:screenPoint]; + NSPoint viewPoint = [s->view convertPoint:winPoint fromView:nil]; + if (NSPointInRect(viewPoint, s->view.bounds)) { + CGFloat scale = s->window.backingScaleFactor; + if (scale < 1.0) scale = 1.0; + pos.x = (int)floor(viewPoint.x * scale); + pos.y = (int)floor((s->view.bounds.size.height - viewPoint.y) * scale); + target = s; + } + break; + } + } + } + } + + if (target != d->drag.hover) { + if (d->drag.hover) { + struct cocoa_surface *old = d->drag.hover; + struct yasl_vector2i old_pos = d->drag.hover_pos; + d->drag.hover = NULL; + if (old->cbs && old->cbs->drag && !old->destroyed) + old->cbs->drag(&old->base, YASL_DRAG_LEAVE, old_pos, old->ud); + if (!d->drag.active) return; + } + if (target && target->destroyed) target = NULL; + d->drag.hover = target; + d->drag.hover_pos = pos; + if (target && !target->destroyed && target->cbs && target->cbs->drag) + target->cbs->drag(&target->base, YASL_DRAG_OVER, pos, target->ud); + } else if (target && !target->destroyed) { + d->drag.hover_pos = pos; + if (target->cbs && target->cbs->drag) + target->cbs->drag(&target->base, YASL_DRAG_OVER, pos, target->ud); + } +} + static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_pointer_kind kind, int button) { @@ -349,6 +466,11 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_pointer } } + if (kind == YASL_PTR_PRESS && button >= 0 && button < 32) + w->buttons |= (1u << button); + else if (kind == YASL_PTR_RELEASE && button >= 0 && button < 32) + w->buttons &= ~(1u << button); + struct yasl_input_event input_ev = { .surface = &w->base, .kind = YASL_INPUT_MOUSE, @@ -362,9 +484,22 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_pointer }, }; input_queue_push(&w->input, &input_ev); - if (kind == YASL_PTR_MOTION && darwin_ctx.drag.active && darwin_ctx.drag.carried) { - cocoa_surface_place_under_pointer(darwin_ctx.drag.carried, darwin_ctx.drag.carry_offset); + if (kind == YASL_PTR_MOTION && darwin_ctx.drag.active) { + if (darwin_ctx.drag.carried) { + cocoa_surface_place_under_pointer(darwin_ctx.drag.carried, darwin_ctx.drag.carry_offset); + } else if (darwin_ctx.drag.image_window) { + CGFloat scale = darwin_ctx.drag.origin && darwin_ctx.drag.origin->window + ? darwin_ctx.drag.origin->window.backingScaleFactor : 1.0; + if (scale < 1.0) scale = 1.0; + NSPoint mouse = [NSEvent mouseLocation]; + CGFloat pt_h = darwin_ctx.drag.image_size.y / scale; + [darwin_ctx.drag.image_window setFrameOrigin:NSMakePoint( + mouse.x - darwin_ctx.drag.image_hotspot.x / scale, + mouse.y - pt_h + darwin_ctx.drag.image_hotspot.y / scale)]; + } + update_drag_hover(&darwin_ctx, [NSEvent mouseLocation]); } else if (kind == YASL_PTR_RELEASE && darwin_ctx.drag.active) { + update_drag_hover(&darwin_ctx, [NSEvent mouseLocation]); drag_end(&darwin_ctx, YASL_DRAG_DROPPED); } if (proximity) { @@ -417,6 +552,18 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) } @end +static struct yasl_vector2i dragging_pos(YaslView *view, id sender) +{ + NSPoint p = [view convertPoint:[sender draggingLocation] fromView:nil]; + NSWindow *win = view.window; + CGFloat scale = win ? win.backingScaleFactor : 1.0; + if (scale < 1.0) scale = 1.0; + return (struct yasl_vector2i){ + .x = (int)floor(p.x * scale), + .y = (int)floor((view.bounds.size.height - p.y) * scale), + }; +} + @implementation YaslView - (instancetype)initWithFrame:(NSRect)frameRect @@ -434,8 +581,14 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) - (NSDragOperation)draggingEntered:(id)sender { NSPasteboard *pb = [sender draggingPasteboard]; - if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) + if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) { + struct cocoa_surface *w = self.surface; + if (w && w->cbs && w->cbs->drop) { + struct yasl_vector2i pos = dragging_pos(self, sender); + w->cbs->drop(&w->base, YASL_DROP_OVER, pos, w->ud); + } return NSDragOperationCopy; + } return NSDragOperationNone; } @@ -444,33 +597,49 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) return [self draggingEntered:sender]; } +- (void)draggingExited:(id)sender +{ + NSPasteboard *pb = [sender draggingPasteboard]; + if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) { + struct cocoa_surface *w = self.surface; + if (w && w->cbs && w->cbs->drop) { + struct yasl_vector2i pos = dragging_pos(self, sender); + w->cbs->drop(&w->base, YASL_DROP_LEAVE, pos, w->ud); + } + } +} + - (BOOL)performDragOperation:(id)sender { struct cocoa_surface *w = self.surface; if (!w) return NO; + struct yasl_vector2i pos = dragging_pos(self, sender); + BOOL ok = NO; + NSPasteboard *pb = [sender draggingPasteboard]; NSArray *urls = [pb readObjectsForClasses:@[[NSURL class]] options:@{NSPasteboardURLReadingFileURLsOnlyKey: @YES}]; - if (!urls.count) - return NO; - - NSMutableString *result = [NSMutableString string]; - for (NSURL *url in urls) { - [result appendString:url.absoluteString]; - [result appendString:@"\r\n"]; - } - const char *utf8 = result.UTF8String; - if (utf8 && strlen(utf8)) { - struct yasl_input_event ev = { - .kind = YASL_INPUT_DROP_FILES, - .surface = &w->base, - .drop_files = { .files = utf8, .len = strlen(utf8) }, - }; - input_queue_push(&w->input, &ev); - cocoa_surface_schedule_redraw(w); - return YES; + if (urls.count) { + NSMutableString *result = [NSMutableString string]; + for (NSURL *url in urls) { + [result appendString:url.absoluteString]; + [result appendString:@"\r\n"]; + } + const char *utf8 = result.UTF8String; + if (utf8 && strlen(utf8)) { + struct yasl_input_event ev = { + .kind = YASL_INPUT_DROP_FILES, + .surface = &w->base, + .drop_files = { .files = utf8, .len = strlen(utf8) }, + }; + input_queue_push(&w->input, &ev); + cocoa_surface_schedule_redraw(w); + ok = YES; + } } - return NO; + if (w->cbs && w->cbs->drop) + w->cbs->drop(&w->base, YASL_DROP_LEAVE, pos, w->ud); + return ok; } - (CALayer *)makeBackingLayer @@ -487,7 +656,7 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) return layer; } -- (BOOL)isOpaque { return YES; } +- (BOOL)isOpaque { return !self.surface || !self.surface->is_popup; } - (BOOL)acceptsFirstResponder { return YES; } - (BOOL)becomeFirstResponder { return YES; } - (BOOL)canBecomeKeyView { return YES; } @@ -1128,7 +1297,8 @@ void cocoa_display_close(struct cocoa_display *d) uint32_t cocoa_display_caps(const struct cocoa_display *d) { - return d ? (YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | YASL_CAP_PLACE_WINDOW | + return d ? (YASL_CAP_POPUPS | YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | + YASL_CAP_PLACE_WINDOW | YASL_CAP_PRIMARY_SELECTION | YASL_CAP_CSD | YASL_CAP_NATIVE_GRAPHICS | YASL_CAP_FRAME_PACING | YASL_CAP_TABLET) : 0; } @@ -1248,8 +1418,25 @@ void cocoa_surface_destroy(struct cocoa_surface *w) { assert([NSThread isMainThread]); if (!w || w->destroyed) return; - if (darwin_ctx.drag.active && (darwin_ctx.drag.origin == w || darwin_ctx.drag.carried == w)) - drag_end(&darwin_ctx, YASL_DRAG_CANCELLED); + if (darwin_ctx.drag.active) { + if (darwin_ctx.drag.origin == w || darwin_ctx.drag.carried == w) + drag_end(&darwin_ctx, YASL_DRAG_CANCELLED); + else if (darwin_ctx.drag.hover == w) + darwin_ctx.drag.hover = NULL; + } + if (w->is_popup && w->parent && w->parent->window) { + [w->parent->window removeChildWindow:w->window]; + w->parent = NULL; + } + for (int i = 0; i < darwin_ctx.count; i++) { + if (darwin_ctx.windows[i]->parent == w) { + if (w->window && darwin_ctx.windows[i]->window) + [w->window removeChildWindow:darwin_ctx.windows[i]->window]; + darwin_ctx.windows[i]->parent = NULL; + } + } + w->buttons = 0; + w->tablet_down = false; darwin_ctx.depth++; bool native_closed = w->closed; static const struct yasl_surface_cbs no_callbacks = {0}; @@ -1349,22 +1536,37 @@ bool cocoa_surface_ime_preedit_cursor(const struct cocoa_surface *w, int *begin, return false; } +static NSPasteboard *pasteboard_for_selection(enum yasl_selection sel) +{ + switch (sel) { + case YASL_CLIPBOARD: + return [NSPasteboard generalPasteboard]; + case YASL_PRIMARY: + return [NSPasteboard pasteboardWithName:@"dev.wsap.yasl.primary"]; + default: + return nil; + } +} + void cocoa_display_copy(struct cocoa_display *d, enum yasl_selection sel, const char *text) { - (void)d; (void)sel; + (void)d; if (!text) return; + NSPasteboard *pb = pasteboard_for_selection(sel); + if (!pb) return; NSString *s = [NSString stringWithUTF8String:text]; if (!s) return; - NSPasteboard *pb = [NSPasteboard generalPasteboard]; [pb clearContents]; [pb setString:s forType:NSPasteboardTypeString]; } void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struct cocoa_surface *target) { - (void)d; (void)sel; + (void)d; if (!target) return; - NSString *s = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]; + NSPasteboard *pb = pasteboard_for_selection(sel); + if (!pb) return; + NSString *s = [pb stringForType:NSPasteboardTypeString]; const char *utf8 = s ? s.UTF8String : NULL; if (utf8) { struct yasl_input_event ev = { @@ -1390,18 +1592,182 @@ struct yasl_geometry cocoa_surface_geometry(const struct cocoa_surface *w) }; } +static bool popup_spec_valid(const struct cocoa_surface *parent, + const struct yasl_popup_spec *spec) +{ + if (!parent || !spec) return false; + int parent_cx = parent->is_popup ? parent->content_offset.x : 0; + int parent_cy = parent->is_popup ? parent->content_offset.y : 0; + int64_t x = (int64_t)spec->anchor.x - parent_cx; + int64_t y = (int64_t)spec->anchor.y - parent_cy; + return x >= -32768 && x <= 32767 && y >= -32768 && y <= 32767 && + spec->anchor.width >= 0 && spec->anchor.height >= 0 && + spec->size.x > 0 && spec->size.y > 0 && + spec->size.x <= 32767 && spec->size.y <= 32767 && + spec->content_offset.x >= 0 && spec->content_offset.y >= 0 && + spec->content_size.x >= 0 && spec->content_size.y >= 0 && + (int64_t)spec->content_offset.x + spec->content_size.x <= spec->size.x && + (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y; +} + +static NSRect popup_calc_frame(struct cocoa_surface *parent, const struct yasl_popup_spec *spec) +{ + CGFloat scale = parent->window ? parent->window.backingScaleFactor : 1.0; + if (scale < 1.0) scale = 1.0; + + CGFloat pcx = (parent->is_popup ? parent->content_offset.x : 0) / scale; + CGFloat pcy = (parent->is_popup ? parent->content_offset.y : 0) / scale; + + CGFloat pw = spec->size.x / scale; + CGFloat ph = spec->size.y / scale; + CGFloat ax = (spec->anchor.x / scale) - pcx; + CGFloat ay = (spec->anchor.y / scale) - pcy; + CGFloat aw = spec->anchor.width / scale; + CGFloat ah = spec->anchor.height / scale; + CGFloat ox = spec->content_offset.x / scale; + CGFloat oy = spec->content_offset.y / scale; + + CGFloat top_left_x = spec->beside ? (ax + aw - ox) : (ax - ox); + CGFloat top_left_y = spec->beside ? (ay - oy) : (ay + ah - oy); + + CGFloat view_h = parent->view.bounds.size.height; + CGFloat view_y = view_h - top_left_y; + + NSPoint ptInView = NSMakePoint(top_left_x, view_y); + NSPoint ptInWin = [parent->view convertPoint:ptInView toView:nil]; + NSPoint screen_tl = [parent->window convertPointToScreen:ptInWin]; + + NSRect frame = NSMakeRect(screen_tl.x, screen_tl.y - ph, pw, ph); + + NSScreen *screen = parent->window.screen ?: [NSScreen mainScreen]; + if (screen) { + NSRect visible = screen.visibleFrame; + if (NSMaxX(frame) > NSMaxX(visible)) { + if (spec->beside) { + CGFloat flip_tl_x = ax - pw + ox; + NSPoint flip_v = NSMakePoint(flip_tl_x, view_y); + NSPoint flip_w = [parent->view convertPoint:flip_v toView:nil]; + NSPoint flip_s = [parent->window convertPointToScreen:flip_w]; + frame.origin.x = flip_s.x; + } + if (NSMaxX(frame) > NSMaxX(visible)) + frame.origin.x = NSMaxX(visible) - frame.size.width; + } + if (NSMinX(frame) < NSMinX(visible)) + frame.origin.x = NSMinX(visible); + + if (NSMinY(frame) < NSMinY(visible)) { + if (!spec->beside) { + CGFloat flip_top_y = ay - ph + oy; + CGFloat flip_vy = view_h - flip_top_y; + NSPoint flip_v = NSMakePoint(top_left_x, flip_vy); + NSPoint flip_w = [parent->view convertPoint:flip_v toView:nil]; + NSPoint flip_s = [parent->window convertPointToScreen:flip_w]; + frame.origin.y = flip_s.y - ph; + } + if (NSMinY(frame) < NSMinY(visible)) + frame.origin.y = NSMinY(visible); + } + if (NSMaxY(frame) > NSMaxY(visible)) + frame.origin.y = NSMaxY(visible) - frame.size.height; + } + return frame; +} + struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, const struct yasl_popup_spec *spec) { - (void)parent; (void)spec; - return NULL; + assert([NSThread isMainThread]); + if (!parent || parent->closed || !parent->window || !parent->view || + !popup_spec_valid(parent, spec)) + return NULL; + + @autoreleasepool { + if (darwin_ctx.count >= darwin_ctx.capacity) { + int cap = darwin_ctx.capacity ? darwin_ctx.capacity * 2 : 16; + struct cocoa_surface **wins = realloc(darwin_ctx.windows, (size_t)cap * sizeof(*wins)); + if (!wins) return NULL; + darwin_ctx.windows = wins; + darwin_ctx.capacity = cap; + } + + struct cocoa_surface *w = calloc(1, sizeof(*w)); + if (!w) return NULL; + w->base.display = parent->base.display; + cocoa_surface_set_callbacks(w, NULL, NULL); + w->clock.refresh_ns = parent->clock.refresh_ns ? parent->clock.refresh_ns : YASL_REFRESH_DEFAULT_NS; + w->parent = parent; + w->is_popup = true; + w->content_offset = spec->content_offset; + + NSRect frame = popup_calc_frame(parent, spec); + NSWindowStyleMask style = NSWindowStyleMaskBorderless; + w->window = [[YaslWindow alloc] initWithContentRect:frame + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + w->window.releasedWhenClosed = NO; + w->window.opaque = NO; + w->window.backgroundColor = [NSColor clearColor]; + w->window.hasShadow = NO; + w->controller = [[YaslWindowController alloc] initWithWindow:w->window]; + w->controller.surface = w; + w->window.delegate = w->controller; + w->window.acceptsMouseMovedEvents = YES; + w->window.colorSpace = [NSColorSpace sRGBColorSpace]; + + w->view = [[YaslView alloc] initWithFrame:w->window.contentView.bounds]; + w->window.contentView = w->view; + w->layer = (CAMetalLayer *)w->view.layer; + w->layer.opaque = NO; + w->layer.device = parent->layer.device; + if (parent->software_queue) { + w->software_queue = [w->layer.device newCommandQueue]; + if (!w->software_queue) { + w->window.delegate = nil; + w->controller.surface = NULL; + [w->view release]; + [w->controller release]; + [w->window release]; + free(w); + return NULL; + } + w->layer.framebufferOnly = NO; + } else { + w->layer.framebufferOnly = parent->layer.framebufferOnly; + } + + w->link = [[w->view displayLinkWithTarget:w->view + selector:@selector(displayLinkFired:)] retain]; + [w->link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + + w->window.surface = w; + w->view.surface = w; + darwin_ctx.windows[darwin_ctx.count++] = w; + + [parent->window addChildWindow:w->window ordered:NSWindowAbove]; + [w->window orderFront:nil]; + + sync_size(w, true); + return w; + } } bool cocoa_surface_popup_reposition(struct cocoa_surface *popup, const struct yasl_popup_spec *spec) { - (void)popup; (void)spec; - return false; + if (!popup || popup->closed || !popup->is_popup || !popup->parent || + !popup->parent->window || !popup->parent->view || + !popup_spec_valid(popup->parent, spec)) + return false; + + @autoreleasepool { + popup->content_offset = spec->content_offset; + NSRect frame = popup_calc_frame(popup->parent, spec); + [popup->window setFrame:frame display:YES]; + sync_size(popup, false); + return true; + } } bool cocoa_surface_start_interactive_move(struct cocoa_surface *s) @@ -1411,18 +1777,83 @@ bool cocoa_surface_start_interactive_move(struct cocoa_surface *s) return true; } +static NSImage *image_from_pixels(const uint32_t *pixels, int width, int height) +{ + if (!pixels || width <= 0 || height <= 0) + return nil; + CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + if (!cs) return nil; + size_t bytes = (size_t)width * (size_t)height * 4; + CGDataProviderRef provider = CGDataProviderCreateWithCopy((const uint8_t *)pixels, bytes); + if (!provider) { + CGColorSpaceRelease(cs); + return nil; + } + CGImageRef cgImage = CGImageCreate((size_t)width, (size_t)height, + 8, 32, (size_t)width * 4, cs, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, + provider, NULL, NO, kCGRenderingIntentDefault); + CGDataProviderRelease(provider); + CGColorSpaceRelease(cs); + if (!cgImage) + return nil; + NSImage *image = [[NSImage alloc] initWithCGImage:cgImage size:NSMakeSize(width, height)]; + CGImageRelease(cgImage); + return image; +} + bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) { - (void)image; if (!d || !origin || d->drag.active || !l) return false; d->drag.active = true; d->drag.origin = origin; d->drag.carried = NULL; + d->drag.hover = NULL; + d->drag.hover_pos = (struct yasl_vector2i){0}; d->drag.listener = *l; d->drag.ud = ud; + [d->drag.image release]; + d->drag.image = nil; + if (d->drag.image_window) { + [d->drag.image_window close]; + [d->drag.image_window release]; + d->drag.image_window = nil; + } + if (image && image->pixels && image->size.x > 0 && image->size.y > 0) { + d->drag.image = image_from_pixels(image->pixels, image->size.x, image->size.y); + d->drag.image_size = image->size; + d->drag.image_hotspot = image->hotspot; + if (d->drag.image) { + CGFloat scale = origin->window ? origin->window.backingScaleFactor : 1.0; + if (scale < 1.0) scale = 1.0; + CGFloat pt_w = image->size.x / scale; + CGFloat pt_h = image->size.y / scale; + NSWindow *win = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, pt_w, pt_h) + styleMask:NSWindowStyleMaskBorderless + backing:NSBackingStoreBuffered + defer:NO]; + win.releasedWhenClosed = NO; + win.opaque = NO; + win.backgroundColor = [NSColor clearColor]; + win.ignoresMouseEvents = YES; + win.level = NSFloatingWindowLevel; + win.hasShadow = YES; + NSImageView *iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, pt_w, pt_h)]; + iv.image = d->drag.image; + iv.imageScaling = NSImageScaleAxesIndependently; + win.contentView = iv; + [iv release]; + d->drag.image_window = win; + NSPoint mouse = [NSEvent mouseLocation]; + [win setFrameOrigin:NSMakePoint(mouse.x - image->hotspot.x / scale, + mouse.y - pt_h + image->hotspot.y / scale)]; + [win orderFront:nil]; + } + } + update_drag_hover(d, [NSEvent mouseLocation]); return true; } @@ -1435,6 +1866,11 @@ bool cocoa_surface_drag_carry(struct cocoa_surface *w, struct yasl_vector2i offs { if (!w || w->closed || !darwin_ctx.drag.active) return false; + if (darwin_ctx.drag.image_window) { + [darwin_ctx.drag.image_window close]; + [darwin_ctx.drag.image_window release]; + darwin_ctx.drag.image_window = nil; + } darwin_ctx.drag.carried = w; darwin_ctx.drag.carry_offset = offset; return cocoa_surface_place_under_pointer(w, offset); @@ -1475,7 +1911,18 @@ void cocoa_surface_set_callbacks(struct cocoa_surface *w, bool cocoa_surface_input_poll(struct cocoa_surface *w, struct yasl_input_event *ev) { - return w && ev && input_queue_pop(&w->input, ev); + if (!w || !ev || !input_queue_pop(&w->input, ev)) + return false; + if (ev->kind == YASL_INPUT_RESET) { + w->buttons = 0; + w->tablet_down = false; + w->mods = 0; + if (w->preedit) { + free(w->preedit); + w->preedit = NULL; + } + } + return true; } uint32_t cocoa_surface_state(const struct cocoa_surface *w) -- cgit v1.2.3