github.com/Seikaijyu/gio@v0.0.1/app/os_macos.m (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // +build darwin,!ios
     4  
     5  #import <AppKit/AppKit.h>
     6  
     7  #include "_cgo_export.h"
     8  
     9  __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
    10  
    11  @interface GioAppDelegate : NSObject<NSApplicationDelegate>
    12  @end
    13  
    14  @interface GioWindowDelegate : NSObject<NSWindowDelegate>
    15  @end
    16  
    17  @implementation GioWindowDelegate
    18  - (void)windowWillMiniaturize:(NSNotification *)notification {
    19  	NSWindow *window = (NSWindow *)[notification object];
    20  	gio_onHide((__bridge CFTypeRef)window.contentView);
    21  }
    22  - (void)windowDidDeminiaturize:(NSNotification *)notification {
    23  	NSWindow *window = (NSWindow *)[notification object];
    24  	gio_onShow((__bridge CFTypeRef)window.contentView);
    25  }
    26  - (void)windowWillEnterFullScreen:(NSNotification *)notification {
    27  	NSWindow *window = (NSWindow *)[notification object];
    28  	gio_onFullscreen((__bridge CFTypeRef)window.contentView);
    29  }
    30  - (void)windowWillExitFullScreen:(NSNotification *)notification {
    31  	NSWindow *window = (NSWindow *)[notification object];
    32  	gio_onWindowed((__bridge CFTypeRef)window.contentView);
    33  }
    34  - (void)windowDidChangeScreen:(NSNotification *)notification {
    35  	NSWindow *window = (NSWindow *)[notification object];
    36  	CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
    37  	CFTypeRef view = (__bridge CFTypeRef)window.contentView;
    38  	gio_onChangeScreen(view, dispID);
    39  }
    40  - (void)windowDidBecomeKey:(NSNotification *)notification {
    41  	NSWindow *window = (NSWindow *)[notification object];
    42  	gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
    43  }
    44  - (void)windowDidResignKey:(NSNotification *)notification {
    45  	NSWindow *window = (NSWindow *)[notification object];
    46  	gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
    47  }
    48  @end
    49  
    50  static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
    51  	NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
    52  	if (!event.hasPreciseScrollingDeltas) {
    53  		// dx and dy are in rows and columns.
    54  		dx *= 10;
    55  		dy *= 10;
    56  	}
    57  	// Origin is in the lower left corner. Convert to upper left.
    58  	CGFloat height = view.bounds.size.height;
    59  	gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
    60  }
    61  
    62  @interface GioView : NSView <CALayerDelegate,NSTextInputClient>
    63  @end
    64  
    65  @implementation GioView
    66  - (void)setFrameSize:(NSSize)newSize {
    67  	[super setFrameSize:newSize];
    68  	[self setNeedsDisplay:YES];
    69  }
    70  // drawRect is called when OpenGL is used, displayLayer otherwise.
    71  // Don't know why.
    72  - (void)drawRect:(NSRect)r {
    73  	gio_onDraw((__bridge CFTypeRef)self);
    74  }
    75  - (void)displayLayer:(CALayer *)layer {
    76  	layer.contentsScale = self.window.backingScaleFactor;
    77  	gio_onDraw((__bridge CFTypeRef)self);
    78  }
    79  - (CALayer *)makeBackingLayer {
    80  	CALayer *layer = gio_layerFactory();
    81  	layer.delegate = self;
    82  	return layer;
    83  }
    84  - (void)viewDidMoveToWindow {
    85  	if (self.window == nil) {
    86  		gio_onClose((__bridge CFTypeRef)self);
    87  	}
    88  }
    89  - (void)mouseDown:(NSEvent *)event {
    90  	handleMouse(self, event, MOUSE_DOWN, 0, 0);
    91  }
    92  - (void)mouseUp:(NSEvent *)event {
    93  	handleMouse(self, event, MOUSE_UP, 0, 0);
    94  }
    95  - (void)rightMouseDown:(NSEvent *)event {
    96  	handleMouse(self, event, MOUSE_DOWN, 0, 0);
    97  }
    98  - (void)rightMouseUp:(NSEvent *)event {
    99  	handleMouse(self, event, MOUSE_UP, 0, 0);
   100  }
   101  - (void)otherMouseDown:(NSEvent *)event {
   102  	handleMouse(self, event, MOUSE_DOWN, 0, 0);
   103  }
   104  - (void)otherMouseUp:(NSEvent *)event {
   105  	handleMouse(self, event, MOUSE_UP, 0, 0);
   106  }
   107  - (void)mouseMoved:(NSEvent *)event {
   108  	handleMouse(self, event, MOUSE_MOVE, 0, 0);
   109  }
   110  - (void)mouseDragged:(NSEvent *)event {
   111  	handleMouse(self, event, MOUSE_MOVE, 0, 0);
   112  }
   113  - (void)rightMouseDragged:(NSEvent *)event {
   114  	handleMouse(self, event, MOUSE_MOVE, 0, 0);
   115  }
   116  - (void)otherMouseDragged:(NSEvent *)event {
   117  	handleMouse(self, event, MOUSE_MOVE, 0, 0);
   118  }
   119  - (void)scrollWheel:(NSEvent *)event {
   120  	CGFloat dx = -event.scrollingDeltaX;
   121  	CGFloat dy = -event.scrollingDeltaY;
   122  	handleMouse(self, event, MOUSE_SCROLL, dx, dy);
   123  }
   124  - (void)keyDown:(NSEvent *)event {
   125  	[self interpretKeyEvents:[NSArray arrayWithObject:event]];
   126  	NSString *keys = [event charactersIgnoringModifiers];
   127  	gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
   128  }
   129  - (void)keyUp:(NSEvent *)event {
   130  	NSString *keys = [event charactersIgnoringModifiers];
   131  	gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
   132  }
   133  - (void)insertText:(id)string {
   134  	gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
   135  }
   136  - (void)doCommandBySelector:(SEL)sel {
   137  	// Don't pass commands up the responder chain.
   138  	// They will end up in a beep.
   139  }
   140  
   141  - (BOOL)hasMarkedText {
   142  	int res = gio_hasMarkedText((__bridge CFTypeRef)self);
   143  	return res ? YES : NO;
   144  }
   145  - (NSRange)markedRange {
   146  	return gio_markedRange((__bridge CFTypeRef)self);
   147  }
   148  - (NSRange)selectedRange {
   149  	return gio_selectedRange((__bridge CFTypeRef)self);
   150  }
   151  - (void)unmarkText {
   152  	gio_unmarkText((__bridge CFTypeRef)self);
   153  }
   154  - (void)setMarkedText:(id)string
   155          selectedRange:(NSRange)selRange
   156       replacementRange:(NSRange)replaceRange {
   157  	NSString *str;
   158  	// string is either an NSAttributedString or an NSString.
   159  	if ([string isKindOfClass:[NSAttributedString class]]) {
   160  		str = [string string];
   161  	} else {
   162  		str = string;
   163  	}
   164  	gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
   165  }
   166  - (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
   167  	return nil;
   168  }
   169  - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
   170                                                  actualRange:(NSRangePointer)actualRange {
   171  	NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
   172  	return [[NSAttributedString alloc] initWithString:str attributes:nil];
   173  }
   174  - (void)insertText:(id)string
   175    replacementRange:(NSRange)replaceRange {
   176  	NSString *str;
   177  	// string is either an NSAttributedString or an NSString.
   178  	if ([string isKindOfClass:[NSAttributedString class]]) {
   179  		str = [string string];
   180  	} else {
   181  		str = string;
   182  	}
   183  	gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
   184  }
   185  - (NSUInteger)characterIndexForPoint:(NSPoint)p {
   186  	return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
   187  }
   188  - (NSRect)firstRectForCharacterRange:(NSRange)rng
   189                           actualRange:(NSRangePointer)actual {
   190      NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
   191      r = [self convertRect:r toView:nil];
   192      return [[self window] convertRectToScreen:r];
   193  }
   194  @end
   195  
   196  // Delegates are weakly referenced from their peers. Nothing
   197  // else holds a strong reference to our window delegate, so
   198  // keep a single global reference instead.
   199  static GioWindowDelegate *globalWindowDel;
   200  
   201  static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
   202  	gio_onFrameCallback(dl);
   203  	return kCVReturnSuccess;
   204  }
   205  
   206  CFTypeRef gio_createDisplayLink(void) {
   207  	CVDisplayLinkRef dl;
   208  	CVDisplayLinkCreateWithActiveCGDisplays(&dl);
   209  	CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
   210  	return dl;
   211  }
   212  
   213  int gio_startDisplayLink(CFTypeRef dl) {
   214  	return CVDisplayLinkStart((CVDisplayLinkRef)dl);
   215  }
   216  
   217  int gio_stopDisplayLink(CFTypeRef dl) {
   218  	return CVDisplayLinkStop((CVDisplayLinkRef)dl);
   219  }
   220  
   221  void gio_releaseDisplayLink(CFTypeRef dl) {
   222  	CVDisplayLinkRelease((CVDisplayLinkRef)dl);
   223  }
   224  
   225  void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
   226  	CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
   227  }
   228  
   229  void gio_hideCursor() {
   230  	@autoreleasepool {
   231  		[NSCursor hide];
   232  	}
   233  }
   234  
   235  void gio_showCursor() {
   236  	@autoreleasepool {
   237  		[NSCursor unhide];
   238  	}
   239  }
   240  
   241  // some cursors are not public, this tries to use a private cursor
   242  // and uses fallback when the use of private cursor fails.
   243  void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
   244  	if ([NSCursor respondsToSelector:cursorName]) {
   245  		id object = [NSCursor performSelector:cursorName];
   246  		if ([object isKindOfClass:[NSCursor class]]) {
   247  			[(NSCursor*)object set];
   248  			return;
   249  		}
   250  	}
   251  	[fallback set];
   252  }
   253  
   254  void gio_setCursor(NSUInteger curID) {
   255  	@autoreleasepool {
   256  		switch (curID) {
   257  			case 0: // pointer.CursorDefault
   258  				[NSCursor.arrowCursor set];
   259  				break;
   260  			// case 1: // pointer.CursorNone
   261  			case 2: // pointer.CursorText
   262  				[NSCursor.IBeamCursor set];
   263  				break;
   264  			case 3: // pointer.CursorVerticalText
   265  				[NSCursor.IBeamCursorForVerticalLayout set];
   266  				break;
   267  			case 4: // pointer.CursorPointer
   268  				[NSCursor.pointingHandCursor set];
   269  				break;
   270  			case 5: // pointer.CursorCrosshair
   271  				[NSCursor.crosshairCursor set];
   272  				break;
   273  			case 6: // pointer.CursorAllScroll
   274  				// For some reason, using _moveCursor fails on Monterey.
   275  				// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
   276  				[NSCursor.arrowCursor set];
   277  				break;
   278  			case 7: // pointer.CursorColResize
   279  				[NSCursor.resizeLeftRightCursor set];
   280  				break;
   281  			case 8: // pointer.CursorRowResize
   282  				[NSCursor.resizeUpDownCursor set];
   283  				break;
   284  			case 9: // pointer.CursorGrab
   285  				// [NSCursor.openHandCursor set];
   286  				gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
   287  				break;
   288  			case 10: // pointer.CursorGrabbing
   289  				// [NSCursor.closedHandCursor set];
   290  				gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
   291  				break;
   292  			case 11: // pointer.CursorNotAllowed
   293  				[NSCursor.operationNotAllowedCursor set];
   294  				break;
   295  			case 12: // pointer.CursorWait
   296  				gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
   297  				break;
   298  			case 13: // pointer.CursorProgress
   299  				gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
   300  				break;
   301  			case 14: // pointer.CursorNorthWestResize
   302  				gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
   303  				break;
   304  			case 15: // pointer.CursorNorthEastResize
   305  				gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
   306  				break;
   307  			case 16: // pointer.CursorSouthWestResize
   308  				gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
   309  				break;
   310  			case 17: // pointer.CursorSouthEastResize
   311  				gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
   312  				break;
   313  			case 18: // pointer.CursorNorthSouthResize
   314  				[NSCursor.resizeUpDownCursor set];
   315  				break;
   316  			case 19: // pointer.CursorEastWestResize
   317  				[NSCursor.resizeLeftRightCursor set];
   318  				break;
   319  			case 20: // pointer.CursorWestResize
   320  				[NSCursor.resizeLeftCursor set];
   321  				break;
   322  			case 21: // pointer.CursorEastResize
   323  				[NSCursor.resizeRightCursor set];
   324  				break;
   325  			case 22: // pointer.CursorNorthResize
   326  				[NSCursor.resizeUpCursor set];
   327  				break;
   328  			case 23: // pointer.CursorSouthResize
   329  				[NSCursor.resizeDownCursor set];
   330  				break;
   331  			case 24: // pointer.CursorNorthEastSouthWestResize
   332  				gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
   333  				break;
   334  			case 25: // pointer.CursorNorthWestSouthEastResize
   335  				gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
   336  				break;
   337  			default:
   338  				[NSCursor.arrowCursor set];
   339  				break;
   340  		}
   341  	}
   342  }
   343  
   344  CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
   345  	@autoreleasepool {
   346  		NSRect rect = NSMakeRect(0, 0, width, height);
   347  		NSUInteger styleMask = NSTitledWindowMask |
   348  			NSResizableWindowMask |
   349  			NSMiniaturizableWindowMask |
   350  			NSClosableWindowMask;
   351  
   352  		NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
   353  													   styleMask:styleMask
   354  														 backing:NSBackingStoreBuffered
   355  														   defer:NO];
   356  		if (minWidth > 0 || minHeight > 0) {
   357  			window.contentMinSize = NSMakeSize(minWidth, minHeight);
   358  		}
   359  		if (maxWidth > 0 || maxHeight > 0) {
   360  			window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
   361  		}
   362  		[window setAcceptsMouseMovedEvents:YES];
   363  		NSView *view = (__bridge NSView *)viewRef;
   364  		[window setContentView:view];
   365  		[window makeFirstResponder:view];
   366  		window.delegate = globalWindowDel;
   367  		return (__bridge_retained CFTypeRef)window;
   368  	}
   369  }
   370  
   371  CFTypeRef gio_createView(void) {
   372  	@autoreleasepool {
   373  		NSRect frame = NSMakeRect(0, 0, 0, 0);
   374  		GioView* view = [[GioView alloc] initWithFrame:frame];
   375  		view.wantsLayer = YES;
   376  		view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
   377  		return CFBridgingRetain(view);
   378  	}
   379  }
   380  
   381  @implementation GioAppDelegate
   382  - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
   383  	[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
   384  	[NSApp activateIgnoringOtherApps:YES];
   385  	gio_onFinishLaunching();
   386  }
   387  - (void)applicationDidHide:(NSNotification *)aNotification {
   388  	gio_onAppHide();
   389  }
   390  - (void)applicationWillUnhide:(NSNotification *)notification {
   391  	gio_onAppShow();
   392  }
   393  @end
   394  
   395  void gio_main() {
   396  	@autoreleasepool {
   397  		[NSApplication sharedApplication];
   398  		GioAppDelegate *del = [[GioAppDelegate alloc] init];
   399  		[NSApp setDelegate:del];
   400  
   401  		NSMenuItem *mainMenu = [NSMenuItem new];
   402  
   403  		NSMenu *menu = [NSMenu new];
   404  		NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
   405  															  action:@selector(hide:)
   406  													   keyEquivalent:@"h"];
   407  		[menu addItem:hideMenuItem];
   408  		NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
   409  															  action:@selector(terminate:)
   410  													   keyEquivalent:@"q"];
   411  		[menu addItem:quitMenuItem];
   412  		[mainMenu setSubmenu:menu];
   413  		NSMenu *menuBar = [NSMenu new];
   414  		[menuBar addItem:mainMenu];
   415  		[NSApp setMainMenu:menuBar];
   416  
   417  		globalWindowDel = [[GioWindowDelegate alloc] init];
   418  
   419  		[NSApp run];
   420  	}
   421  }