diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 13:44:13 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 13:44:13 +0300 |
| commit | c059e3aa7f93da470f052a5cd8ee0d8dc969e679 (patch) | |
| tree | 63ffa378287ed2c606eda2e940501b9edc3d2b8b /src/platform/cocoa/window.m | |
| parent | 87d0bc50d53dc216efb2b04495f090616318bc5c (diff) | |
rename and simplification
Diffstat (limited to 'src/platform/cocoa/window.m')
| -rw-r--r-- | src/platform/cocoa/window.m | 2227 |
1 files changed, 2227 insertions, 0 deletions
diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m new file mode 100644 index 0000000..3c8519a --- /dev/null +++ b/src/platform/cocoa/window.m @@ -0,0 +1,2227 @@ +#import <AppKit/AppKit.h> +#import <Metal/Metal.h> +#import <QuartzCore/QuartzCore.h> + +#include "platform/cocoa/api.h" +#include "yasl/native/cocoa.h" +#include "common/timing.h" +#include "common/loop.h" +#include "platform/cocoa/keycodes.h" +#include "common/keyboard.h" +#include "keymap.h" + +#include <assert.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LINK_IDLE_FRAMES 8 +#define SCROLL_POINTS_PER_LINE 10.0 + +enum { + DEVICE_LCTL = 0x0001, + DEVICE_LSHIFT = 0x0002, + DEVICE_RSHIFT = 0x0004, + DEVICE_LCMD = 0x0008, + DEVICE_RCMD = 0x0010, + DEVICE_LALT = 0x0020, + DEVICE_RALT = 0x0040, + DEVICE_RCTL = 0x2000, +}; + +@interface YaslView : NSView <NSTextInputClient> +@property (nonatomic, assign) struct cocoa_surface *surface; +@end + +@interface YaslAppDelegate : NSObject <NSApplicationDelegate> +@end + +@interface YaslWindow : NSWindow +@property (nonatomic, assign) struct cocoa_surface *surface; +@end + +@interface YaslWindowController : NSWindowController <NSWindowDelegate> +@property (nonatomic, assign) struct cocoa_surface *surface; +@property (nonatomic, assign) BOOL fullscreenTransition; +@end + +#include "common/input.h" + +struct cocoa_surface { + struct yasl_surface base; + YaslWindow *window; + YaslWindowController *controller; + YaslView *view; + CAMetalLayer *layer; + id<MTLCommandQueue> software_queue; + id<MTLBuffer> software_buffer; + NSUInteger software_buffer_capacity; + CADisplayLink *link; + + struct cocoa_surface *parent; + bool is_popup; + struct yasl_vector2i content_offset; + + const struct yasl_window_cbs *cbs; + void *ud; + uint64_t frames_skipped; + + char *preedit; + struct yasl_rect2i ime_caret; + + struct yasl_vector2i size; + uint32_t scale_120; + uint32_t bg_color; + uint32_t *pixels; + size_t pixel_capacity; + + id<CAMetalDrawable> drawable; + bool transaction; + struct yasl_frame frame; + struct yasl_damage_list damage; + bool did_present; + unsigned idle_frames; + bool want_draw; + bool frame_due; + bool unpaced; + bool rendering; + bool closed; + bool destroyed; + struct cocoa_surface *dead_next; + + struct input_queue input; + struct yasl_vector2i pointer; + 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; + + char title[160]; + struct yasl_frame_clock clock; +}; + +struct cocoa_display { + struct yasl_display base; + struct cocoa_surface **windows; + int count; + int capacity; + const struct yasl_display_cbs *cbs; + void *ud; + char *app_id; + struct yasl_loop *loop; + id key_up_monitor; + NSCursor *hidden_cursor; + YaslAppDelegate *delegate; + NSPoint cascade; + struct cocoa_surface *dead; + struct { + bool active; + 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; + bool launched; + bool pumping; + bool owns_loop; +}; + +static struct cocoa_display darwin_ctx; + +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); +} + +static void render_and_commit(struct cocoa_surface *w); +static void reap_dead(struct cocoa_display *d); + +static void set_state(struct cocoa_surface *w, uint32_t bit, bool on) +{ + if (!w || w->closed) + return; + uint32_t next = on ? (w->state | bit) : (w->state & ~bit); + if (next == w->state) + return; + w->state = next; + darwin_ctx.depth++; + if (w->cbs->state) + w->cbs->state(&w->base, w->state, w->ud); + 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, + .focus = { .focused = focused }, + }; + input_queue_push(&w->input, &ev); + darwin_ctx.depth++; + set_state(w, YASL_STATE_ACTIVATED, focused); + cocoa_surface_schedule_redraw(w); + darwin_ctx.depth--; +} + +static void sync_size(struct cocoa_surface *w, bool force) +{ + if (!w || !w->layer || w->closed) + return; + uint64_t start = yasl_loop_now_ns(); + NSRect bounds = w->view.bounds; + CGFloat scale = w->window.backingScaleFactor; + if (scale < 1.0) + scale = 1.0; + struct yasl_vector2i size = { + .x = (int)lround(bounds.size.width * scale), + .y = (int)lround(bounds.size.height * scale), + }; + uint32_t scale_120 = (uint32_t)lround(scale * YASL_SCALE_UNIT); + bool rescaled = force || scale_120 != w->scale_120; + bool resized = force || (size.x != w->size.x || size.y != w->size.y); + if (!rescaled && !resized) + return; + darwin_ctx.depth++; + w->size = size; + w->scale_120 = scale_120; + w->layer.contentsScale = scale; + w->layer.drawableSize = CGSizeMake(size.x, size.y); + if (w->cbs->configure) + w->cbs->configure(&w->base, (struct yasl_geometry){ + .size = size, .scale_120 = scale_120, + }, w->ud); + cocoa_surface_schedule_redraw(w); + if (w->view.inLiveResize) + render_and_commit(w); + yasl_clock_note_resize(&w->clock, yasl_loop_now_ns() - start); + darwin_ctx.depth--; +} + +static unsigned cocoa_mods(NSEventModifierFlags m) +{ + unsigned mods = 0; + if (m & NSEventModifierFlagShift) mods |= YASL_MOD_SHIFT; + if (m & NSEventModifierFlagOption) mods |= YASL_MOD_ALT; + if (m & NSEventModifierFlagControl) mods |= YASL_MOD_CTRL; + if (m & NSEventModifierFlagCommand) mods |= YASL_MOD_SUPER; + if (m & NSEventModifierFlagCapsLock) mods |= YASL_MOD_CAPS; + return mods; +} + +/* Decode a single Unicode scalar, never a lone UTF-16 surrogate. Layout labels + * are optional: multi-scalar mappings and native function-key characters are 0. */ +static uint32_t cocoa_key_label(NSString *characters) +{ + if (!characters.length) return 0; + unichar first = [characters characterAtIndex:0]; + if (characters.length == 1) + return yasl_key_label(first); + if (characters.length == 2 && first >= 0xd800 && first <= 0xdbff) { + unichar second = [characters characterAtIndex:1]; + if (second >= 0xdc00 && second <= 0xdfff) + return yasl_key_label(0x10000 + ((first - 0xd800) << 10) + second - 0xdc00); + } + return 0; +} + +static struct yasl_key_event cocoa_key_event(NSEvent *ev, enum yasl_key_action action) +{ + struct yasl_key_event k = { + .key = yasl_key_from_cocoa(ev.keyCode), + .action = action, + .mods = cocoa_mods(ev.modifierFlags), + .native_code = ev.keyCode, + }; + /* characters properties are invalid on flagsChanged events. */ + if (ev.type == NSEventTypeKeyDown || ev.type == NSEventTypeKeyUp) { + k.codepoint = cocoa_key_label([ev charactersByApplyingModifiers:0]); + k.shifted = cocoa_key_label([ev charactersByApplyingModifiers:NSEventModifierFlagShift]); + } + return k; +} + +static void on_key_event(struct cocoa_surface *w, NSEvent *ev, enum yasl_key_action action) +{ + w->mods = cocoa_mods(ev.modifierFlags); + struct yasl_key_event k = cocoa_key_event(ev, action); + struct yasl_input_event input_ev = { + .kind = YASL_INPUT_KEY, + .surface = &w->base, + .key = k, + }; + input_queue_push(&w->input, &input_ev); +} + +static void on_text_input(struct cocoa_surface *w, const char *text, size_t len) +{ + if (len) { + struct yasl_input_event ev = { + .kind = YASL_INPUT_TEXT, + .surface = &w->base, + .text = { .text = text, .len = len }, + }; + input_queue_push(&w->input, &ev); + } +} + +static bool modifier_down(NSEvent *ev) +{ + NSEventModifierFlags f = ev.modifierFlags; + switch (ev.keyCode) { + case kVK_Shift: return f & DEVICE_LSHIFT; + case kVK_RightShift: return f & DEVICE_RSHIFT; + case kVK_Control: return f & DEVICE_LCTL; + case kVK_RightControl: return f & DEVICE_RCTL; + case kVK_Option: return f & DEVICE_LALT; + case kVK_RightOption: return f & DEVICE_RALT; + case kVK_Command: return f & DEVICE_LCMD; + case kVK_RightCommand: return f & DEVICE_RCMD; + case kVK_CapsLock: return f & NSEventModifierFlagCapsLock; + default: return false; + } +} + +static int cocoa_button(NSInteger number) +{ + switch (number) { + case 0: return 0; + case 1: return 2; + case 2: return 1; + default: return -1; + } +} + +static struct yasl_vector2i event_pos(struct cocoa_surface *w, NSEvent *ev) +{ + NSPoint p = [w->view convertPoint:ev.locationInWindow fromView:nil]; + CGFloat scale = w->window.backingScaleFactor; + if (scale < 1.0) + scale = 1.0; + return (struct yasl_vector2i){ + .x = (int)floor(p.x * scale), + .y = (int)floor((w->view.bounds.size.height - p.y) * scale), + }; +} + +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) +{ + if (!w || w->closed || button < 0) + return; + bool proximity = ev.type == NSEventTypeTabletProximity || + ev.subtype == NSEventSubtypeTabletProximity; + if (proximity) { + if (ev.isEnteringProximity) { + w->tablet_device = ev.pointingDeviceType == NSPointingDeviceTypeEraser ? + YASL_PTR_DEV_ERASER : ev.pointingDeviceType == NSPointingDeviceTypePen ? + YASL_PTR_DEV_PEN : YASL_PTR_DEV_MOUSE; + return; + } + kind = YASL_PTR_LEAVE; + button = 3; + } + w->mods = cocoa_mods(ev.modifierFlags); + if (kind != YASL_PTR_LEAVE) + w->pointer = event_pos(w, ev); + + enum yasl_pointer_device device = YASL_PTR_DEV_MOUSE; + struct yasl_pointer_pen pen = {0}; + + if (kind == YASL_PTR_LEAVE && w->tablet_device != YASL_PTR_DEV_MOUSE) { + device = w->tablet_device; + w->tablet_down = false; + } else if (ev.type == NSEventTypeTabletPoint || ev.subtype == NSEventSubtypeTabletPoint) { + /* pointingDeviceType belongs to proximity events. Remember that + * event's tool so erasers and zero-pressure hover stay classified. */ + device = w->tablet_device == YASL_PTR_DEV_ERASER ? YASL_PTR_DEV_ERASER : YASL_PTR_DEV_PEN; + float press = (float)ev.pressure; + if (press > 1.0f) press = 1.0f; + if (press > 0.0f) + pen.pressure = (uint16_t)(press * 65535.0f); + + NSPoint tilt = ev.tilt; + pen.tilt_x = (int8_t)lround(fmax(-1, fmin(1, tilt.x)) * 90.0); + pen.tilt_y = (int8_t)lround(fmax(-1, fmin(1, -tilt.y)) * 90.0); + if (button == 0 && kind == YASL_PTR_PRESS) w->tablet_down = true; + if (button == 0 && kind == YASL_PTR_RELEASE) { + w->tablet_down = false; + pen.pressure = 0; + } + } + + 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, + .mouse = { + .pos = w->pointer, + .button = (int16_t)button, + .kind = kind, + .device = device, + .mods = w->mods, + .pen = pen, + }, + }; + input_queue_push(&w->input, &input_ev); + 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) { + w->tablet_device = YASL_PTR_DEV_MOUSE; + w->tablet_down = false; + } +} + +static NSCursor *cocoa_cursor(enum yasl_cursor cursor) +{ + switch (cursor) { + case YASL_CURSOR_TEXT: return [NSCursor IBeamCursor]; + case YASL_CURSOR_LINK: return [NSCursor pointingHandCursor]; + case YASL_CURSOR_CROSSHAIR: return [NSCursor crosshairCursor]; + case YASL_CURSOR_HIDDEN: + if (!darwin_ctx.hidden_cursor) { + NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(1, 1)]; + [image lockFocus]; + [[NSColor clearColor] set]; + NSRectFillUsingOperation(NSMakeRect(0, 0, 1, 1), NSCompositingOperationCopy); + [image unlockFocus]; + darwin_ctx.hidden_cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSZeroPoint]; + [image release]; + } + return darwin_ctx.hidden_cursor; + case YASL_CURSOR_ARROW: + default: return [NSCursor arrowCursor]; + } +} + +@implementation YaslAppDelegate +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + (void)sender; + if (darwin_ctx.cbs && darwin_ctx.cbs->quit) + darwin_ctx.cbs->quit(darwin_ctx.ud); + return NSTerminateCancel; +} +- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)visible +{ + (void)sender; + if (!visible && darwin_ctx.cbs && darwin_ctx.cbs->reopen) + darwin_ctx.cbs->reopen(darwin_ctx.ud); + return YES; +} +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + (void)sender; + return NO; +} +@end + +static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> 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 +{ + self = [super initWithFrame:frameRect]; + if (self) { + self.wantsLayer = YES; + self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever; + self.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; + [self registerForDraggedTypes:@[NSPasteboardTypeFileURL]]; + } + return self; +} + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)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_OVER, pos, w->ud); + } + return NSDragOperationCopy; + } + return NSDragOperationNone; +} + +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender +{ + return [self draggingEntered:sender]; +} + +- (void)draggingExited:(id<NSDraggingInfo>)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<NSDraggingInfo>)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<NSURL *> *urls = [pb readObjectsForClasses:@[[NSURL class]] + options:@{NSPasteboardURLReadingFileURLsOnlyKey: @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; + } + } + if (w->cbs && w->cbs->drop) + w->cbs->drop(&w->base, YASL_DROP_LEAVE, pos, w->ud); + return ok; +} + +- (CALayer *)makeBackingLayer +{ + CAMetalLayer *layer = [CAMetalLayer layer]; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer.framebufferOnly = YES; + layer.allowsNextDrawableTimeout = YES; + layer.opaque = YES; + layer.contentsGravity = kCAGravityTopLeft; + CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + layer.colorspace = srgb; + CGColorSpaceRelease(srgb); + return layer; +} + +- (BOOL)isOpaque { return !self.surface || !self.surface->is_popup; } +- (BOOL)acceptsFirstResponder { return YES; } +- (BOOL)becomeFirstResponder { return YES; } +- (BOOL)canBecomeKeyView { return YES; } +- (BOOL)wantsUpdateLayer { return YES; } + +- (void)setFrameSize:(NSSize)size +{ + [super setFrameSize:size]; + sync_size(self.surface, false); +} + +- (void)viewDidChangeBackingProperties +{ + [super viewDidChangeBackingProperties]; + sync_size(self.surface, false); +} + +- (void)viewWillStartLiveResize +{ + [super viewWillStartLiveResize]; + struct cocoa_surface *w = self.surface; + if (w) + w->layer.presentsWithTransaction = YES; +} + +- (void)viewDidEndLiveResize +{ + [super viewDidEndLiveResize]; + struct cocoa_surface *w = self.surface; + if (!w) + return; + w->layer.presentsWithTransaction = NO; + cocoa_surface_schedule_redraw(w); +} + +- (void)displayLinkFired:(CADisplayLink *)link +{ + struct cocoa_surface *w = self.surface; + if (!w || w->closed) + return; + CFTimeInterval period = link.targetTimestamp - link.timestamp; + if (period > 0.0 && period < 0.25) + w->clock.refresh_ns = period * 1e9; + w->frame_due = true; + if (!w->want_draw || (w->state & YASL_STATE_SUSPENDED)) { + if (++w->idle_frames >= LINK_IDLE_FRAMES) + link.paused = YES; + return; + } + w->idle_frames = 0; + if (darwin_ctx.loop) + yasl_loop_kick(darwin_ctx.loop); +} + +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + if (self.trackingAreas.count > 0) + return; + NSTrackingArea *area = [[NSTrackingArea alloc] + initWithRect:NSZeroRect + options:NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | + NSTrackingCursorUpdate | NSTrackingActiveAlways | + NSTrackingInVisibleRect + owner:self + userInfo:nil]; + [self addTrackingArea:area]; + [area release]; +} + +- (void)cursorUpdate:(NSEvent *)event +{ + (void)event; + struct cocoa_surface *w = self.surface; + [cocoa_cursor(w ? w->cursor : YASL_CURSOR_ARROW) set]; +} + +- (void)keyDown:(NSEvent *)event +{ + struct cocoa_surface *w = self.surface; + if (!w) return; + on_key_event(w, event, event.isARepeat ? YASL_KEY_REPEAT : YASL_KEY_PRESS); + [self interpretKeyEvents:@[event]]; +} + +- (void)keyUp:(NSEvent *)event +{ + struct cocoa_surface *w = self.surface; + if (w) + on_key_event(w, event, YASL_KEY_RELEASE); +} + +- (void)flagsChanged:(NSEvent *)event +{ + struct cocoa_surface *w = self.surface; + if (!w) return; + w->mods = cocoa_mods(event.modifierFlags); + on_key_event(w, event, modifier_down(event) ? YASL_KEY_PRESS : YASL_KEY_RELEASE); +} + +- (void)mouseDown:(NSEvent *)event +{ + [self.window makeFirstResponder:self]; + push_pointer(self.surface, event, YASL_PTR_PRESS, cocoa_button(event.buttonNumber)); +} +- (void)rightMouseDown:(NSEvent *)event { [self mouseDown:event]; } +- (void)otherMouseDown:(NSEvent *)event { [self mouseDown:event]; } + +- (void)mouseUp:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_RELEASE, cocoa_button(event.buttonNumber)); +} +- (void)rightMouseUp:(NSEvent *)event { [self mouseUp:event]; } +- (void)otherMouseUp:(NSEvent *)event { [self mouseUp:event]; } + +- (void)mouseMoved:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_MOTION, 3); +} + +- (void)mouseDragged:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_MOTION, 0); +} +- (void)rightMouseDragged:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_MOTION, 2); +} +- (void)otherMouseDragged:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_MOTION, cocoa_button(event.buttonNumber)); +} + +- (void)mouseExited:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_LEAVE, 3); +} + +- (void)tabletProximity:(NSEvent *)event +{ + push_pointer(self.surface, event, YASL_PTR_MOTION, 3); +} + +- (void)tabletPoint:(NSEvent *)event +{ + struct cocoa_surface *w = self.surface; + if (!w) return; + bool down = (event.buttonMask & NSEventButtonMaskPenTip) != 0; + enum yasl_pointer_kind kind = down == w->tablet_down ? YASL_PTR_MOTION : + down ? YASL_PTR_PRESS : YASL_PTR_RELEASE; + push_pointer(w, event, kind, 0); +} + +- (void)scrollWheel:(NSEvent *)event +{ + struct cocoa_surface *w = self.surface; + if (!w) return; + double dx = -event.scrollingDeltaX, dy = -event.scrollingDeltaY; + if (event.hasPreciseScrollingDeltas) { + dx /= SCROLL_POINTS_PER_LINE; + dy /= SCROLL_POINTS_PER_LINE; + } + if (!isfinite(dx) || !isfinite(dy) || (dx == 0 && dy == 0)) return; + w->mods = cocoa_mods(event.modifierFlags); + w->pointer = event_pos(w, event); + struct yasl_input_event input_ev = { + .kind = YASL_INPUT_MOUSE, + .surface = &w->base, + .mouse = { + .pos = w->pointer, + .kind = YASL_PTR_SCROLL, + .mods = w->mods, + .scroll = {dx, dy}, + }, + }; + input_queue_push(&w->input, &input_ev); +} + +static NSString *plain_string(id string) +{ + return [string isKindOfClass:[NSAttributedString class]] + ? [(NSAttributedString *)string string] : (NSString *)string; +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + (void)replacementRange; + struct cocoa_surface *w = self.surface; + if (!w) return; + const char *utf8 = plain_string(string).UTF8String; + size_t len = utf8 ? strlen(utf8) : 0; + free(w->preedit); + w->preedit = NULL; + on_text_input(w, utf8, len); + cocoa_surface_schedule_redraw(w); +} + +- (void)doCommandBySelector:(SEL)selector { (void)selector; } + +- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange + replacementRange:(NSRange)replacementRange +{ + (void)selectedRange; + (void)replacementRange; + struct cocoa_surface *w = self.surface; + if (!w) return; + NSString *str = plain_string(string); + const char *utf8 = str.length ? str.UTF8String : NULL; + free(w->preedit); + w->preedit = utf8 ? strdup(utf8) : NULL; + cocoa_surface_schedule_redraw(w); +} + +- (void)unmarkText +{ + struct cocoa_surface *w = self.surface; + if (!w || !w->preedit) return; + free(w->preedit); + w->preedit = NULL; + cocoa_surface_schedule_redraw(w); +} + +- (NSRange)selectedRange { return NSMakeRange(NSNotFound, 0); } +- (NSRange)markedRange +{ + struct cocoa_surface *w = self.surface; + if (!w || !w->preedit) + return NSMakeRange(NSNotFound, 0); + return NSMakeRange(0, [NSString stringWithUTF8String:w->preedit].length); +} +- (BOOL)hasMarkedText { return self.surface && self.surface->preedit != NULL; } +- (nullable NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range + actualRange:(nullable NSRangePointer)actualRange +{ + (void)range; (void)actualRange; + return nil; +} +- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText { return @[]; } +- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange +{ + (void)range; (void)actualRange; + struct cocoa_surface *w = self.surface; + if (!w || !w->window) return NSZeroRect; + CGFloat scale = w->window.backingScaleFactor; + if (scale < 1.0) scale = 1.0; + NSRect r = NSMakeRect(w->ime_caret.x / scale, + self.bounds.size.height - (w->ime_caret.y + w->ime_caret.height) / scale, + w->ime_caret.width / scale, w->ime_caret.height / scale); + return [w->window convertRectToScreen:[self convertRect:r toView:nil]]; +} +- (NSUInteger)characterIndexForPoint:(NSPoint)point { (void)point; return 0; } + +@end + +@implementation YaslWindow + +- (void)sendEvent:(NSEvent *)event +{ + darwin_ctx.depth++; + @try { + [super sendEvent:event]; + } @finally { + darwin_ctx.depth--; + } +} + +@end + +@implementation YaslWindowController + +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + (void)notification; + if (self.surface) push_focus(self.surface, true); +} + +- (void)windowDidResignKey:(NSNotification *)notification +{ + (void)notification; + if (self.surface) push_focus(self.surface, false); +} + +- (void)windowDidChangeOcclusionState:(NSNotification *)notification +{ + (void)notification; + struct cocoa_surface *w = self.surface; + if (!w) return; + bool visible = (self.window.occlusionState & NSWindowOcclusionStateVisible) != 0; + darwin_ctx.depth++; + set_state(w, YASL_STATE_SUSPENDED, !visible); + if (visible) + cocoa_surface_schedule_redraw(w); + else + w->link.paused = YES; + darwin_ctx.depth--; +} + +- (void)windowWillEnterFullScreen:(NSNotification *)notification +{ + (void)notification; + self.fullscreenTransition = YES; +} + +- (void)windowWillExitFullScreen:(NSNotification *)notification +{ + (void)notification; + self.fullscreenTransition = YES; +} + +- (void)syncWindowState +{ + self.fullscreenTransition = NO; + struct cocoa_surface *w = self.surface; + if (!w || w->closed) + return; + darwin_ctx.depth++; + bool fullscreen = (self.window.styleMask & NSWindowStyleMaskFullScreen) != 0; + set_state(w, YASL_STATE_FULLSCREEN, fullscreen); + set_state(w, YASL_STATE_MAXIMIZED, !fullscreen && self.window.isZoomed); + sync_size(w, false); + darwin_ctx.depth--; +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { [self syncWindowState]; } +- (void)windowDidExitFullScreen:(NSNotification *)notification { [self syncWindowState]; } +- (void)windowDidFailToEnterFullScreen:(NSWindow *)window { (void)window; [self syncWindowState]; } +- (void)windowDidFailToExitFullScreen:(NSWindow *)window { (void)window; [self syncWindowState]; } + +- (void)windowDidResize:(NSNotification *)notification +{ + (void)notification; + if (!self.fullscreenTransition) + [self syncWindowState]; +} + +- (void)windowDidChangeScreen:(NSNotification *)notification +{ + (void)notification; + sync_size(self.surface, false); +} + +- (void)windowWillClose:(NSNotification *)notification +{ + (void)notification; + struct cocoa_surface *w = self.surface; + if (!w || w->closed) + return; + darwin_ctx.depth++; + w->closed = true; + [w->link invalidate]; + if (w->cbs->close) + w->cbs->close(&w->base, w->ud); + darwin_ctx.depth--; +} + +@end + +bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffer *fb, + const struct yasl_damage_list *damage) +{ + if (!w || w->closed || !w->rendering || w->did_present || !fb || !damage || + fb->pixels != w->frame.target.pixels || fb->size.x != w->size.x || + fb->size.y != w->size.y || !w->drawable) + return false; + if (w->software_queue) { + if (fb->pixels != w->pixels || fb->size.x != w->size.x || fb->size.y != w->size.y) + return false; + NSUInteger row_bytes = ((NSUInteger)w->size.x * 4 + 255) & ~(NSUInteger)255; + NSUInteger length = row_bytes * (NSUInteger)w->size.y; + if (!w->software_buffer || w->software_buffer_capacity < length) { + [w->software_buffer release]; + w->software_buffer = [w->layer.device newBufferWithLength:length + options:MTLResourceStorageModeShared]; + w->software_buffer_capacity = w->software_buffer ? length : 0; + } + id<MTLBuffer> buffer = w->software_buffer; + if (!buffer) return false; + for (int y = 0; y < w->size.y; y++) + memcpy((char *)buffer.contents + (NSUInteger)y * row_bytes, + w->pixels + (size_t)y * w->size.x, (size_t)w->size.x * 4); + id<MTLCommandBuffer> command = [w->software_queue commandBuffer]; + id<MTLBlitCommandEncoder> blit = [command blitCommandEncoder]; + if (!blit) return false; + id<CAMetalDrawable> drawable = w->drawable; + [blit copyFromBuffer:buffer sourceOffset:0 sourceBytesPerRow:row_bytes + sourceBytesPerImage:length sourceSize:MTLSizeMake(w->size.x, w->size.y, 1) + toTexture:drawable.texture destinationSlice:0 destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [blit endEncoding]; + [command presentDrawable:drawable]; + [command commit]; + } + yasl_clock_presented(&w->clock, yasl_loop_now_ns(), false); + w->did_present = true; + return true; +} + +static void render_and_commit(struct cocoa_surface *w) +{ + if (w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || + (w->state & YASL_STATE_SUSPENDED)) + return; + + size_t count = (size_t)w->size.x * (size_t)w->size.y; + if (w->software_queue && count > w->pixel_capacity) { + uint32_t *pixels = realloc(w->pixels, count * sizeof(*pixels)); + if (!pixels) return; + w->pixels = pixels; + w->pixel_capacity = count; + } + darwin_ctx.depth++; + w->rendering = true; + w->want_draw = false; + w->frame_due = false; + w->did_present = false; + id<CAMetalDrawable> drawable = [w->layer nextDrawable]; + w->drawable = drawable; + w->transaction = w->layer.presentsWithTransaction; + w->damage = (struct yasl_damage_list){0}; + w->frame = (struct yasl_frame){ + .surface = &w->base, + .target = { + .generation = yasl_frame_begin(&w->base), + .pixels = w->software_queue ? w->pixels : NULL, + .size = w->size, + }, + }; + if (drawable) + w->cbs->draw(&w->base, &w->frame, w->ud); + if (!w->closed && !w->did_present) { + w->want_draw = true; + w->frames_skipped++; + } + w->drawable = nil; + w->rendering = false; + darwin_ctx.depth--; +} + +static void pump_window(struct cocoa_surface *w) +{ + if (!w || w->closed || !w->want_draw || !w->cbs->draw) + return; + if (w->link && !w->frame_due && !w->unpaced) + return; + @autoreleasepool { render_and_commit(w); } +} + +int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) +{ + assert([NSThread isMainThread]); + if (!d || d->pumping) + return -1; + for (int i = 0; i < d->count; i++) { + struct cocoa_surface *w = d->windows[i]; + if (w->input.count || w->input.reset_pending || + (w->want_draw && (!w->link || w->frame_due || w->unpaced))) { + timeout_ms = 0; + break; + } + } + d->pumping = true; + d->depth++; + int count = 0; + @try { + if (d->loop) { + int timer_to = yasl_loop_next_timeout_ms(d->loop); + if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) { + timeout_ms = timer_to; + } + } + + NSDate *limitDate; + if (timeout_ms == 0) + limitDate = [NSDate distantPast]; + else if (timeout_ms < 0) + limitDate = [NSDate distantFuture]; + else + limitDate = [NSDate dateWithTimeIntervalSinceNow:(double)timeout_ms / 1000.0]; + + @autoreleasepool { + NSEvent *ev = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:limitDate + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (ev) { + count++; + [NSApp sendEvent:ev]; + [NSApp updateWindows]; + + while ((ev = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES])) { + count++; + [NSApp sendEvent:ev]; + [NSApp updateWindows]; + } + } + } + + if (d->loop) { + yasl_loop_step(d->loop, 0); + } + } @finally { + d->depth--; + d->pumping = false; + } + return count; +} + +void cocoa_display_flush(struct cocoa_display *d) +{ + assert([NSThread isMainThread]); + if (!d) return; + d->depth++; + for (int i = d->count - 1; i >= 0; i--) { + if (i < d->count) + pump_window(d->windows[i]); + } + @autoreleasepool { + [NSApp updateWindows]; + } + if (--d->depth == 0) + reap_dead(d); +} + +void cocoa_surface_schedule_redraw(struct cocoa_surface *w) +{ + if (!w || w->closed) return; + w->want_draw = true; + w->idle_frames = 0; + if (w->link.paused) { + w->frame_due = true; + w->link.paused = NO; + } + if (darwin_ctx.loop) + yasl_loop_kick(darwin_ctx.loop); +} + +static void app_init(const char *app_name) +{ + if (darwin_ctx.launched) return; + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + NSWindow.allowsAutomaticWindowTabbing = NO; + darwin_ctx.delegate = [[YaslAppDelegate alloc] init]; + NSApp.delegate = darwin_ctx.delegate; + + NSString *ns_name = app_name ? [NSString stringWithUTF8String:app_name] : @"YASL"; + NSMenu *menu = [[NSMenu alloc] init]; + NSMenuItem *app_item = [[NSMenuItem alloc] init]; + NSMenu *app_menu = [[NSMenu alloc] initWithTitle:ns_name]; + [app_menu addItemWithTitle:[NSString stringWithFormat:@"About %@", ns_name] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [app_menu addItem:[NSMenuItem separatorItem]]; + [app_menu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", ns_name] + action:@selector(terminate:) + keyEquivalent:@"q"]; + app_item.submenu = app_menu; + [menu addItem:app_item]; + [app_menu release]; + [app_item release]; + NSApp.mainMenu = menu; + [menu release]; + + darwin_ctx.key_up_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp + handler:^NSEvent *(NSEvent *event) { + if (!(event.modifierFlags & NSEventModifierFlagCommand) || !event.window) + return event; + [event.window sendEvent:event]; + return nil; + }]; + [NSApp finishLaunching]; + darwin_ctx.launched = true; +} + +static void place_window(NSWindow *window) +{ + if (!darwin_ctx.count) { + [window center]; + NSRect frame = window.frame; + darwin_ctx.cascade = NSMakePoint(frame.origin.x, frame.origin.y + frame.size.height); + return; + } + darwin_ctx.cascade = [window cascadeTopLeftFromPoint:darwin_ctx.cascade]; +} + +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +{ + assert([NSThread isMainThread]); + if (darwin_ctx.open) return NULL; + const char *name = spec && spec->app_id ? spec->app_id : "YASL"; + @autoreleasepool { app_init(name); } + darwin_ctx.base.ops = &yasl_cocoa_backend; + darwin_ctx.base.native_handle = (__bridge void *)[NSApplication sharedApplication]; + darwin_ctx.open = true; + if (!loop) { + darwin_ctx.loop = yasl_loop_create(); + darwin_ctx.owns_loop = true; + } else { + darwin_ctx.loop = loop; + darwin_ctx.owns_loop = false; + } + darwin_ctx.base.loop = darwin_ctx.loop; + if (spec) { + darwin_ctx.base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0}; + darwin_ctx.cbs = &darwin_ctx.base.callbacks; + darwin_ctx.ud = spec->ud; + if (spec->app_id) darwin_ctx.app_id = strdup(spec->app_id); + } + return &darwin_ctx; +} + +void cocoa_display_close(struct cocoa_display *d) +{ + if (!d) return; + drag_end(d, YASL_DRAG_CANCELLED); + assert(!d->count && "destroy every surface before its display"); + reap_dead(d); + if (d->key_up_monitor) { + [NSEvent removeMonitor:d->key_up_monitor]; + d->key_up_monitor = nil; + } + if (d->owns_loop && d->loop) { + yasl_loop_destroy(d->loop); + d->loop = NULL; + d->base.loop = NULL; + } + NSApp.delegate = nil; + [d->delegate release]; + d->delegate = nil; + [d->hidden_cursor release]; + free(d->app_id); + free(d->windows); + *d = (struct cocoa_display){0}; +} + +uint32_t cocoa_display_caps(const struct cocoa_display *d) +{ + 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; +} + +struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, + const struct yasl_window_spec *spec, void *graphics_device) +{ + assert([NSThread isMainThread]); + if (!d || !d->open || !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 = &d->base; + cocoa_surface_set_callbacks(w, NULL, NULL); + w->clock.refresh_ns = YASL_REFRESH_DEFAULT_NS; + const char *t = spec->title ? spec->title : "YASL"; + strncpy(w->title, t, sizeof(w->title) - 1); + + int win_w = spec->size.x > 0 ? spec->size.x : 800; + int win_h = spec->size.y > 0 ? spec->size.y : 480; + NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + w->window = [[YaslWindow alloc] initWithContentRect:NSMakeRect(0, 0, win_w, win_h) + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + w->window.releasedWhenClosed = NO; + w->base.native_handle = (__bridge void *)w->window; + w->base.size = w->size; + w->base.scale_120 = w->scale_120; + w->controller = [[YaslWindowController alloc] initWithWindow:w->window]; + w->controller.surface = w; + w->window.delegate = w->controller; + w->window.acceptsMouseMovedEvents = YES; + w->window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary; + w->window.titlebarAppearsTransparent = YES; + w->window.title = [NSString stringWithUTF8String:w->title] ?: @"YASL"; + 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; + if (graphics_device) { + w->layer.device = (id<MTLDevice>)graphics_device; + } else { + id<MTLDevice> device = MTLCreateSystemDefaultDevice(); + w->layer.device = device; + w->software_queue = [device newCommandQueue]; + [device release]; + if (!w->software_queue) { + w->window.delegate = nil; + w->controller.surface = NULL; + [w->window close]; + [w->view release]; + [w->controller release]; + [w->window release]; + free(w); + return NULL; + } + w->layer.framebufferOnly = NO; + } + + w->link = [[w->view displayLinkWithTarget:w->view + selector:@selector(displayLinkFired:)] retain]; + [w->link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + + place_window(w->window); + w->window.surface = w; + w->view.surface = w; + darwin_ctx.windows[darwin_ctx.count++] = w; + + [w->window makeKeyAndOrderFront:nil]; + [w->window makeFirstResponder:w->view]; + [NSApp activate]; + + sync_size(w, true); + if (spec->tearing) { + w->layer.displaySyncEnabled = NO; + w->unpaced = true; + } + if (spec->fullscreen) + [w->window toggleFullScreen:nil]; + return w; + } +} + +static void surface_free(struct cocoa_surface *w) +{ + [w->link release]; + [w->software_buffer release]; + [w->software_queue release]; + [w->view release]; + [w->controller release]; + [w->window release]; + free(w->preedit); + free(w->pixels); + free(w); +} + +static void reap_dead(struct cocoa_display *d) +{ + assert(d->depth == 0); + while (d->dead) { + struct cocoa_surface *w = d->dead; + d->dead = w->dead_next; + surface_free(w); + } +} + +void cocoa_surface_destroy(struct cocoa_surface *w) +{ + assert([NSThread isMainThread]); + if (!w || w->destroyed) return; + 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_window_cbs no_callbacks = {0}; + w->destroyed = true; + w->closed = true; + w->cbs = &no_callbacks; + w->ud = NULL; + [w->link invalidate]; + w->window.delegate = nil; + w->controller.surface = NULL; + w->window.surface = NULL; + w->view.surface = NULL; + if (!native_closed) + [w->window close]; + input_queue_clear(&w->input); + w->dead_next = darwin_ctx.dead; + darwin_ctx.dead = w; + + for (int i = 0; i < darwin_ctx.count; i++) { + if (darwin_ctx.windows[i] == w) { + darwin_ctx.windows[i] = darwin_ctx.windows[--darwin_ctx.count]; + break; + } + } + if (!darwin_ctx.count) { + free(darwin_ctx.windows); + darwin_ctx.windows = NULL; + darwin_ctx.capacity = 0; + darwin_ctx.cascade = NSZeroPoint; + } + darwin_ctx.depth--; + if (darwin_ctx.loop) + yasl_loop_kick(darwin_ctx.loop); +} + +struct cocoa_display *cocoa_surface_display(const struct cocoa_surface *w) +{ + return w ? &darwin_ctx : NULL; +} + +void cocoa_surface_set_title(struct cocoa_surface *w, const char *title) +{ + if (!w || w->closed || !title) return; + strncpy(w->title, title, sizeof(w->title) - 1); + w->window.title = [NSString stringWithUTF8String:w->title] ?: @"YASL"; +} + +void cocoa_surface_set_background_color(struct cocoa_surface *w, uint32_t bg) +{ + if (!w || w->closed) return; + w->bg_color = bg; + double r = ((bg >> 16) & 0xff) / 255.0; + double g = ((bg >> 8) & 0xff) / 255.0; + double b = (bg & 0xff) / 255.0; + w->window.backgroundColor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:1.0]; + bool dark = 0.299 * r + 0.587 * g + 0.114 * b < 0.5; + w->window.appearance = [NSAppearance appearanceNamed: + dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua]; +} + +void cocoa_surface_set_cursor(struct cocoa_surface *w, enum yasl_cursor cursor) +{ + if (!w || w->closed || cursor == w->cursor || (unsigned)cursor > YASL_CURSOR_HIDDEN) return; + w->cursor = cursor; + NSPoint p = [w->view convertPoint:w->window.mouseLocationOutsideOfEventStream fromView:nil]; + if (w->window.isKeyWindow && NSPointInRect(p, w->view.bounds)) + [cocoa_cursor(cursor) set]; +} + +bool cocoa_surface_place_under_pointer(struct cocoa_surface *w, struct yasl_vector2i at) +{ + if (!w || w->closed || !w->window) return false; + CGFloat scale = w->window.backingScaleFactor; + if (scale < 1.0) scale = 1.0; + NSPoint mouse = [NSEvent mouseLocation]; + NSRect content = [w->window contentRectForFrameRect:w->window.frame]; + NSPoint origin = NSMakePoint(mouse.x - at.x / scale, + mouse.y - content.size.height + at.y / scale); + [w->window setFrameOrigin:[w->window frameRectForContentRect: + (NSRect){ .origin = origin, .size = content.size }].origin]; + return true; +} + +void cocoa_surface_set_ime_caret(struct cocoa_surface *w, struct yasl_rect2i caret) +{ + if (w) w->ime_caret = caret; +} + +const char *cocoa_surface_ime_preedit(const struct cocoa_surface *w) +{ + return w ? w->preedit : NULL; +} + +bool cocoa_surface_ime_preedit_cursor(const struct cocoa_surface *w, int *begin, int *end) +{ + (void)w; (void)begin; (void)end; + 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; + if (!text) return; + NSPasteboard *pb = pasteboard_for_selection(sel); + if (!pb) return; + NSString *s = [NSString stringWithUTF8String:text]; + if (!s) return; + [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; + if (!target) return; + 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 = { + .kind = YASL_INPUT_PASTE, + .surface = &target->base, + .paste = { .text = utf8, .len = strlen(utf8) }, + }; + input_queue_push(&target->input, &ev); + } +} + +int cocoa_display_modifiers(const struct cocoa_display *d) +{ + (void)d; + return (int)cocoa_mods(NSEvent.modifierFlags); +} + +struct yasl_geometry cocoa_surface_geometry(const struct cocoa_surface *w) +{ + return (struct yasl_geometry){ + .size = w ? w->size : (struct yasl_vector2i){0}, + .scale_120 = w ? w->scale_120 : YASL_SCALE_UNIT, + }; +} + +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) +{ + 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->base.native_handle = (__bridge void *)w->window; + w->base.size = w->size; + w->base.scale_120 = w->scale_120; + 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) +{ + 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) +{ + if (!s || !s->window) return false; + [s->window performWindowDragWithEvent:[NSApp currentEvent]]; + 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; + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pixels, (CFIndex)bytes); + CGDataProviderRef provider = data ? CGDataProviderCreateWithCFData(data) : NULL; + if (data) + CFRelease(data); + 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) +{ + 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; +} + +void cocoa_display_drag_cancel(struct cocoa_display *d) +{ + drag_end(d, YASL_DRAG_CANCELLED); +} + +bool cocoa_surface_drag_carry(struct cocoa_surface *w, struct yasl_vector2i offset) +{ + 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); +} + +bool cocoa_surface_start_interactive_resize(struct cocoa_surface *s, enum yasl_resize_edge edge) +{ + (void)s; (void)edge; + return false; +} + +bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_vector2i pos) +{ + (void)s; (void)pos; + return false; +} + +void *cocoa_display_get_native_handle(struct cocoa_display *d) +{ + return d ? (void *)NSApp : NULL; +} + +void *cocoa_surface_get_native_handle(struct cocoa_surface *s) +{ + return s ? (void *)s->window : NULL; +} + + + +void cocoa_surface_set_callbacks(struct cocoa_surface *w, + const struct yasl_window_cbs *cbs, void *ud) +{ + if (!w || w->closed) return; + w->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + w->cbs = &w->base.callbacks; + w->ud = ud; +} + +bool cocoa_surface_input_poll(struct cocoa_surface *w, struct yasl_input_event *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) +{ + return w ? w->state : 0; +} + +struct yasl_timing cocoa_surface_timing(const struct cocoa_surface *w) +{ + struct yasl_timing timing = {0}; + if (w) { + timing.refresh_ns = w->clock.refresh_ns; + timing.last_present_ns = w->clock.last_present_ns; + timing.next_present_ns = yasl_clock_next_present_ns(&w->clock); + timing.frames_skipped = w->frames_skipped; + timing.source = w->clock.source; + yasl_clock_resize_stats(&w->clock, &timing.resize); + } + return timing; +} + +uint64_t cocoa_surface_frame_time_ns(struct cocoa_surface *w, uint64_t now) +{ + return w ? yasl_clock_frame_timestamp_ns(&w->clock, now) : now; +} + +static struct yasl_display *impl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +{ + struct cocoa_display *result = cocoa_display_open(loop, spec); + return result ? &result->base : NULL; +} + +static void impl_display_close(struct yasl_display *d) +{ + cocoa_display_close((struct cocoa_display *)d); +} + +static uint32_t impl_display_caps(const struct yasl_display *d) +{ + return cocoa_display_caps((const struct cocoa_display *)d); +} + +static int impl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms) +{ + return cocoa_display_dispatch_timeout((struct cocoa_display *)d, timeout_ms); +} + +static void impl_display_flush(struct yasl_display *d) +{ + cocoa_display_flush((struct cocoa_display *)d); +} + +static int impl_display_modifiers(const struct yasl_display *d) +{ + return cocoa_display_modifiers((const struct cocoa_display *)d); +} + +static void impl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) +{ + cocoa_display_copy((struct cocoa_display *)d, sel, text); +} + +static void impl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target) +{ + cocoa_display_paste((struct cocoa_display *)d, sel, (struct cocoa_surface *)target); +} + +static bool impl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) +{ + return cocoa_display_drag_start((struct cocoa_display *)d, (struct cocoa_surface *)origin, image, l, ud); +} + +static void impl_display_drag_cancel(struct yasl_display *d) +{ + cocoa_display_drag_cancel((struct cocoa_display *)d); +} + +static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d, + const struct yasl_window_spec *spec, void *graphics_device) +{ + struct cocoa_surface *result = cocoa_surface_create_toplevel((struct cocoa_display *)d, spec, graphics_device); + return result ? &result->base : NULL; +} + +static struct yasl_surface *impl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec) +{ + struct cocoa_surface *result = cocoa_surface_create_popup((struct cocoa_surface *)parent, spec); + return result ? &result->base : NULL; +} + +static bool impl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec) +{ + return cocoa_surface_popup_reposition((struct cocoa_surface *)popup, spec); +} + +static void impl_surface_destroy(struct yasl_surface *s) +{ + cocoa_surface_destroy((struct cocoa_surface *)s); +} + +static struct yasl_display *impl_surface_display(const struct yasl_surface *s) +{ + struct cocoa_display *result = cocoa_surface_display((const struct cocoa_surface *)s); + return result ? &result->base : NULL; +} + +static void impl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud) +{ + cocoa_surface_set_callbacks((struct cocoa_surface *)s, cbs, ud); +} + +static void impl_surface_set_title(struct yasl_surface *s, const char *title) +{ + cocoa_surface_set_title((struct cocoa_surface *)s, title); +} + +static void impl_surface_schedule_redraw(struct yasl_surface *s) +{ + cocoa_surface_schedule_redraw((struct cocoa_surface *)s); +} + +static bool impl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage) +{ + return cocoa_surface_present((struct cocoa_surface *)s, fb, damage); +} + +static bool impl_surface_input_poll(struct yasl_surface *s, struct yasl_input_event *ev) +{ + return cocoa_surface_input_poll((struct cocoa_surface *)s, ev); +} + +static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s) +{ + return cocoa_surface_geometry((const struct cocoa_surface *)s); +} + +static uint32_t impl_surface_state(const struct yasl_surface *s) +{ + return cocoa_surface_state((const struct cocoa_surface *)s); +} + +static struct yasl_timing impl_surface_timing(const struct yasl_surface *s) +{ + return cocoa_surface_timing((const struct cocoa_surface *)s); +} + +static uint64_t impl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now) +{ + return cocoa_surface_frame_time_ns((struct cocoa_surface *)s, now); +} + +static void impl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor) +{ + cocoa_surface_set_cursor((struct cocoa_surface *)s, cursor); +} + +static void impl_surface_set_background_color(struct yasl_surface *s, uint32_t bg) +{ + cocoa_surface_set_background_color((struct cocoa_surface *)s, bg); +} + +static const char *impl_surface_ime_preedit(const struct yasl_surface *s) +{ + return cocoa_surface_ime_preedit((const struct cocoa_surface *)s); +} + +static bool impl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end) +{ + return cocoa_surface_ime_preedit_cursor((const struct cocoa_surface *)s, begin, end); +} + +static void impl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret) +{ + cocoa_surface_set_ime_caret((struct cocoa_surface *)s, caret); +} + +static bool impl_surface_start_interactive_move(struct yasl_surface *s) +{ + return cocoa_surface_start_interactive_move((struct cocoa_surface *)s); +} + +static bool impl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge) +{ + return cocoa_surface_start_interactive_resize((struct cocoa_surface *)s, edge); +} + +static bool impl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos) +{ + return cocoa_surface_show_window_menu((struct cocoa_surface *)s, pos); +} + +static bool impl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset) +{ + return cocoa_surface_drag_carry((struct cocoa_surface *)s, offset); +} + +static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at) +{ + return cocoa_surface_place_under_pointer((struct cocoa_surface *)s, at); +} + +static void *impl_display_get_native_handle(struct yasl_display *d) +{ + return cocoa_display_get_native_handle((struct cocoa_display *)d); +} + +static void *impl_surface_get_native_handle(struct yasl_surface *s) +{ + return cocoa_surface_get_native_handle((struct cocoa_surface *)s); +} + +static bool impl_surface_get_native_frame(struct yasl_surface *s, + const struct yasl_framebuffer *target, struct yasl_native_frame *out) +{ + struct cocoa_surface *w = (struct cocoa_surface *)s; + if (w->closed || !w->rendering || w->did_present || !w->drawable || + target->pixels != w->frame.target.pixels || + target->size.x != w->size.x || target->size.y != w->size.y) + return false; + *out = (struct yasl_native_frame){(void *)w->drawable, w->transaction}; + return true; +} + +const struct yasl_backend_ops yasl_cocoa_backend = { + .type = YASL_BACKEND_COCOA, + .display_open = impl_display_open, + .display_close = impl_display_close, + .display_caps = impl_display_caps, + .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_flush = impl_display_flush, + .display_modifiers = impl_display_modifiers, + .display_copy = impl_display_copy, + .display_paste = impl_display_paste, + .display_drag_start = impl_display_drag_start, + .display_drag_cancel = impl_display_drag_cancel, + .surface_create_toplevel = impl_surface_create_toplevel, + .surface_create_popup = impl_surface_create_popup, + .surface_popup_reposition = impl_surface_popup_reposition, + .surface_destroy = impl_surface_destroy, + .surface_display = impl_surface_display, + .surface_set_callbacks = impl_surface_set_callbacks, + .surface_set_title = impl_surface_set_title, + .surface_schedule_redraw = impl_surface_schedule_redraw, + .surface_present = impl_surface_present, + .surface_input_poll = impl_surface_input_poll, + .surface_geometry = impl_surface_geometry, + .surface_state = impl_surface_state, + .surface_timing = impl_surface_timing, + .surface_frame_time_ns = impl_surface_frame_time_ns, + .surface_set_cursor = impl_surface_set_cursor, + .surface_set_background_color = impl_surface_set_background_color, + .surface_ime_preedit = impl_surface_ime_preedit, + .surface_ime_preedit_cursor = impl_surface_ime_preedit_cursor, + .surface_set_ime_caret = impl_surface_set_ime_caret, + .surface_start_interactive_move = impl_surface_start_interactive_move, + .surface_start_interactive_resize = impl_surface_start_interactive_resize, + .surface_show_window_menu = impl_surface_show_window_menu, + .surface_drag_carry = impl_surface_drag_carry, + .surface_place_under_pointer = impl_surface_place_under_pointer, + .display_get_native_handle = impl_display_get_native_handle, + .surface_get_native_handle = impl_surface_get_native_handle, + .surface_get_native_frame = impl_surface_get_native_frame, +}; + +NSApplication *yasl_cocoa_display_get_application(struct yasl_display *d) +{ + return (d && yasl_display_backend(d) == YASL_BACKEND_COCOA) ? + (NSApplication *)yasl_display_get_native_handle(d) : nil; +} + +NSWindow *yasl_cocoa_surface_get_window(struct yasl_surface *s) +{ + return (s && s->display && yasl_display_backend(s->display) == YASL_BACKEND_COCOA) ? + (NSWindow *)yasl_surface_get_native_handle(s) : nil; +} + +NSView *yasl_cocoa_surface_get_view(struct yasl_surface *s) +{ + NSWindow *w = yasl_cocoa_surface_get_window(s); + return w ? w.contentView : nil; +} |
