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