github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/wm/os_macos.m (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // +build darwin,!ios
     4  
     5  @import AppKit;
     6  
     7  #include "_cgo_export.h"
     8  
     9  @interface GioAppDelegate : NSObject<NSApplicationDelegate>
    10  @end
    11  
    12  @interface GioWindowDelegate : NSObject<NSWindowDelegate>
    13  @end
    14  
    15  @implementation GioWindowDelegate
    16  - (void)windowWillMiniaturize:(NSNotification *)notification {
    17  	NSWindow *window = (NSWindow *)[notification object];
    18  	gio_onHide((__bridge CFTypeRef)window.contentView);
    19  }
    20  - (void)windowDidDeminiaturize:(NSNotification *)notification {
    21  	NSWindow *window = (NSWindow *)[notification object];
    22  	gio_onShow((__bridge CFTypeRef)window.contentView);
    23  }
    24  - (void)windowDidChangeScreen:(NSNotification *)notification {
    25  	NSWindow *window = (NSWindow *)[notification object];
    26  	CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
    27  	CFTypeRef view = (__bridge CFTypeRef)window.contentView;
    28  	gio_onChangeScreen(view, dispID);
    29  }
    30  - (void)windowDidBecomeKey:(NSNotification *)notification {
    31  	NSWindow *window = (NSWindow *)[notification object];
    32  	gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
    33  }
    34  - (void)windowDidResignKey:(NSNotification *)notification {
    35  	NSWindow *window = (NSWindow *)[notification object];
    36  	gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
    37  }
    38  - (void)windowWillClose:(NSNotification *)notification {
    39  	NSWindow *window = (NSWindow *)[notification object];
    40  	window.delegate = nil;
    41  	gio_onClose((__bridge CFTypeRef)window.contentView);
    42  }
    43  @end
    44  
    45  static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
    46  	NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
    47  	if (!event.hasPreciseScrollingDeltas) {
    48  		// dx and dy are in rows and columns.
    49  		dx *= 10;
    50  		dy *= 10;
    51  	}
    52  	// Origin is in the lower left corner. Convert to upper left.
    53  	CGFloat height = view.bounds.size.height;
    54  	gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
    55  }
    56  
    57  @interface GioView : NSView
    58  @end
    59  
    60  @implementation GioView
    61  - (void)drawRect:(NSRect)r {
    62  	gio_onDraw((__bridge CFTypeRef)self);
    63  }
    64  - (void)mouseDown:(NSEvent *)event {
    65  	handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
    66  }
    67  - (void)mouseUp:(NSEvent *)event {
    68  	handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
    69  }
    70  - (void)middleMouseDown:(NSEvent *)event {
    71  	handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
    72  }
    73  - (void)middletMouseUp:(NSEvent *)event {
    74  	handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
    75  }
    76  - (void)rightMouseDown:(NSEvent *)event {
    77  	handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
    78  }
    79  - (void)rightMouseUp:(NSEvent *)event {
    80  	handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
    81  }
    82  - (void)mouseMoved:(NSEvent *)event {
    83  	handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
    84  }
    85  - (void)mouseDragged:(NSEvent *)event {
    86  	handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
    87  }
    88  - (void)scrollWheel:(NSEvent *)event {
    89  	CGFloat dx = -event.scrollingDeltaX;
    90  	CGFloat dy = -event.scrollingDeltaY;
    91  	handleMouse(self, event, GIO_MOUSE_SCROLL, dx, dy);
    92  }
    93  - (void)keyDown:(NSEvent *)event {
    94  	NSString *keys = [event charactersIgnoringModifiers];
    95  	gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], true);
    96  	[self interpretKeyEvents:[NSArray arrayWithObject:event]];
    97  }
    98  - (void)keyUp:(NSEvent *)event {
    99  	NSString *keys = [event charactersIgnoringModifiers];
   100  	gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], false);
   101  }
   102  - (void)insertText:(id)string {
   103  	const char *utf8 = [string UTF8String];
   104  	gio_onText((__bridge CFTypeRef)self, (char *)utf8);
   105  }
   106  - (void)doCommandBySelector:(SEL)sel {
   107  	// Don't pass commands up the responder chain.
   108  	// They will end up in a beep.
   109  }
   110  @end
   111  // Delegates are weakly referenced from their peers. Nothing
   112  // else holds a strong reference to our window delegate, so
   113  // keep a single global reference instead.
   114  static GioWindowDelegate *globalWindowDel;
   115  
   116  void gio_writeClipboard(unichar *chars, NSUInteger length) {
   117  	@autoreleasepool {
   118  		NSString *s = [NSString string];
   119  		if (length > 0) {
   120  			s = [NSString stringWithCharacters:chars length:length];
   121  		}
   122  		NSPasteboard *p = NSPasteboard.generalPasteboard;
   123  		[p declareTypes:@[NSPasteboardTypeString] owner:nil];
   124  		[p setString:s forType:NSPasteboardTypeString];
   125  	}
   126  }
   127  
   128  CFTypeRef gio_readClipboard(void) {
   129  	@autoreleasepool {
   130  		NSPasteboard *p = NSPasteboard.generalPasteboard;
   131  		NSString *content = [p stringForType:NSPasteboardTypeString];
   132  		return (__bridge_retained CFTypeRef)content;
   133  	}
   134  }
   135  
   136  CGFloat gio_viewHeight(CFTypeRef viewRef) {
   137  	NSView *view = (__bridge NSView *)viewRef;
   138  	return [view bounds].size.height;
   139  }
   140  
   141  CGFloat gio_viewWidth(CFTypeRef viewRef) {
   142  	NSView *view = (__bridge NSView *)viewRef;
   143  	return [view bounds].size.width;
   144  }
   145  
   146  CGFloat gio_getScreenBackingScale(void) {
   147  	return [NSScreen.mainScreen backingScaleFactor];
   148  }
   149  
   150  CGFloat gio_getViewBackingScale(CFTypeRef viewRef) {
   151  	NSView *view = (__bridge NSView *)viewRef;
   152  	return [view.window backingScaleFactor];
   153  }
   154  
   155  void gio_setNeedsDisplay(CFTypeRef viewRef) {
   156  	NSView *view = (__bridge NSView *)viewRef;
   157  	[view setNeedsDisplay:YES];
   158  }
   159  
   160  void gio_hideCursor() {
   161  	@autoreleasepool {
   162  		[NSCursor hide];
   163  	}
   164  }
   165  
   166  void gio_showCursor() {
   167  	@autoreleasepool {
   168  		[NSCursor unhide];
   169  	}
   170  }
   171  
   172  void gio_setCursor(NSUInteger curID) {
   173  	@autoreleasepool {
   174  		switch (curID) {
   175  			case 1:
   176  				[NSCursor.arrowCursor set];
   177  				break;
   178  			case 2:
   179  				[NSCursor.IBeamCursor set];
   180  				break;
   181  			case 3:
   182  				[NSCursor.pointingHandCursor set];
   183  				break;
   184  			case 4:
   185  				[NSCursor.crosshairCursor set];
   186  				break;
   187  			case 5:
   188  				[NSCursor.resizeLeftRightCursor set];
   189  				break;
   190  			case 6:
   191  				[NSCursor.resizeUpDownCursor set];
   192  				break;
   193  			case 7:
   194  				[NSCursor.openHandCursor set];
   195  				break;
   196  			default:
   197  				[NSCursor.arrowCursor set];
   198  				break;
   199  		}
   200  	}
   201  }
   202  
   203  static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
   204  	gio_onFrameCallback(dl);
   205  	return kCVReturnSuccess;
   206  }
   207  
   208  CFTypeRef gio_createDisplayLink(void) {
   209  	CVDisplayLinkRef dl;
   210  	CVDisplayLinkCreateWithActiveCGDisplays(&dl);
   211  	CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
   212  	return dl;
   213  }
   214  
   215  int gio_startDisplayLink(CFTypeRef dl) {
   216  	return CVDisplayLinkStart((CVDisplayLinkRef)dl);
   217  }
   218  
   219  int gio_stopDisplayLink(CFTypeRef dl) {
   220  	return CVDisplayLinkStop((CVDisplayLinkRef)dl);
   221  }
   222  
   223  void gio_releaseDisplayLink(CFTypeRef dl) {
   224  	CVDisplayLinkRelease((CVDisplayLinkRef)dl);
   225  }
   226  
   227  void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
   228  	CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
   229  }
   230  
   231  NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
   232  	NSWindow *window = (__bridge NSWindow *)windowRef;
   233  	return [window cascadeTopLeftFromPoint:topLeft];
   234  }
   235  
   236  void gio_makeKeyAndOrderFront(CFTypeRef windowRef) {
   237  	NSWindow *window = (__bridge NSWindow *)windowRef;
   238  	[window makeKeyAndOrderFront:nil];
   239  }
   240  
   241  void gio_toggleFullScreen(CFTypeRef windowRef) {
   242  	NSWindow *window = (__bridge NSWindow *)windowRef;
   243  	[window toggleFullScreen:nil];
   244  }
   245  
   246  CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
   247  	@autoreleasepool {
   248  		NSRect rect = NSMakeRect(0, 0, width, height);
   249  		NSUInteger styleMask = NSTitledWindowMask |
   250  			NSResizableWindowMask |
   251  			NSMiniaturizableWindowMask |
   252  			NSClosableWindowMask;
   253  
   254  		NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
   255  													   styleMask:styleMask
   256  														 backing:NSBackingStoreBuffered
   257  														   defer:NO];
   258  		if (minWidth > 0 || minHeight > 0) {
   259  			window.contentMinSize = NSMakeSize(minWidth, minHeight);
   260  		}
   261  		if (maxWidth > 0 || maxHeight > 0) {
   262  			window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
   263  		}
   264  		[window setAcceptsMouseMovedEvents:YES];
   265  		if (title != nil) {
   266  			window.title = [NSString stringWithUTF8String: title];
   267  		}
   268  		NSView *view = (__bridge NSView *)viewRef;
   269  		[window setContentView:view];
   270  		[window makeFirstResponder:view];
   271  		window.releasedWhenClosed = NO;
   272  		window.delegate = globalWindowDel;
   273  		return (__bridge_retained CFTypeRef)window;
   274  	}
   275  }
   276  
   277  void gio_close(CFTypeRef windowRef) {
   278  	NSWindow* window = (__bridge NSWindow *)windowRef;
   279  	[window performClose:nil];
   280  }
   281  
   282  void gio_setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
   283  	NSWindow* window = (__bridge NSWindow *)windowRef;
   284  	NSSize size = NSMakeSize(width, height);
   285  	[window setContentSize:size];
   286  }
   287  
   288  void gio_setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
   289  	NSWindow* window = (__bridge NSWindow *)windowRef;
   290  	window.contentMinSize = NSMakeSize(width, height);
   291  }
   292  
   293  void gio_setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
   294  	NSWindow* window = (__bridge NSWindow *)windowRef;
   295  	window.contentMaxSize = NSMakeSize(width, height);
   296  }
   297  
   298  void gio_setTitle(CFTypeRef windowRef, const char *title) {
   299  	NSWindow* window = (__bridge NSWindow *)windowRef;
   300  	window.title = [NSString stringWithUTF8String: title];
   301  }
   302  
   303  CFTypeRef gio_layerForView(CFTypeRef viewRef) {
   304  	NSView *view = (__bridge NSView *)viewRef;
   305  	return (__bridge CFTypeRef)view.layer;
   306  }
   307  
   308  CFTypeRef gio_createView(void) {
   309  	@autoreleasepool {
   310  		NSRect frame = NSMakeRect(0, 0, 0, 0);
   311  		GioView* view = [[GioView alloc] initWithFrame:frame];
   312  		[view setWantsLayer:YES];
   313  		return CFBridgingRetain(view);
   314  	}
   315  }
   316  
   317  @implementation GioAppDelegate
   318  - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
   319  	[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
   320  	[NSApp activateIgnoringOtherApps:YES];
   321  	gio_onFinishLaunching();
   322  }
   323  - (void)applicationDidHide:(NSNotification *)aNotification {
   324  	gio_onAppHide();
   325  }
   326  - (void)applicationWillUnhide:(NSNotification *)notification {
   327  	gio_onAppShow();
   328  }
   329  @end
   330  
   331  void gio_main() {
   332  	@autoreleasepool {
   333  		[NSApplication sharedApplication];
   334  		GioAppDelegate *del = [[GioAppDelegate alloc] init];
   335  		[NSApp setDelegate:del];
   336  
   337  		NSMenuItem *mainMenu = [NSMenuItem new];
   338  
   339  		NSMenu *menu = [NSMenu new];
   340  		NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
   341  															  action:@selector(hide:)
   342  													   keyEquivalent:@"h"];
   343  		[menu addItem:hideMenuItem];
   344  		NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
   345  															  action:@selector(terminate:)
   346  													   keyEquivalent:@"q"];
   347  		[menu addItem:quitMenuItem];
   348  		[mainMenu setSubmenu:menu];
   349  		NSMenu *menuBar = [NSMenu new];
   350  		[menuBar addItem:mainMenu];
   351  		[NSApp setMainMenu:menuBar];
   352  
   353  		globalWindowDel = [[GioWindowDelegate alloc] init];
   354  
   355  		[NSApp run];
   356  	}
   357  }