gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/os_macos.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  //go:build darwin && !ios
     4  // +build darwin,!ios
     5  
     6  package app
     7  
     8  import (
     9  	"errors"
    10  	"image"
    11  	"io"
    12  	"runtime"
    13  	"runtime/cgo"
    14  	"strings"
    15  	"time"
    16  	"unicode"
    17  	"unicode/utf8"
    18  
    19  	"gioui.org/internal/f32"
    20  	"gioui.org/io/event"
    21  	"gioui.org/io/key"
    22  	"gioui.org/io/pointer"
    23  	"gioui.org/io/system"
    24  	"gioui.org/io/transfer"
    25  	"gioui.org/op"
    26  	"gioui.org/unit"
    27  
    28  	_ "gioui.org/internal/cocoainit"
    29  )
    30  
    31  /*
    32  #cgo CFLAGS: -Werror -Wno-deprecated-declarations -fobjc-arc -x objective-c
    33  #cgo LDFLAGS: -framework AppKit -framework QuartzCore
    34  
    35  #include <AppKit/AppKit.h>
    36  
    37  #define MOUSE_MOVE 1
    38  #define MOUSE_UP 2
    39  #define MOUSE_DOWN 3
    40  #define MOUSE_SCROLL 4
    41  
    42  __attribute__ ((visibility ("hidden"))) void gio_main(void);
    43  __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
    44  __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
    45  __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
    46  
    47  static void writeClipboard(CFTypeRef str) {
    48  	@autoreleasepool {
    49  		NSString *s = (__bridge NSString *)str;
    50  		NSPasteboard *p = NSPasteboard.generalPasteboard;
    51  		[p declareTypes:@[NSPasteboardTypeString] owner:nil];
    52  		[p setString:s forType:NSPasteboardTypeString];
    53  	}
    54  }
    55  
    56  static CFTypeRef readClipboard(void) {
    57  	@autoreleasepool {
    58  		NSPasteboard *p = NSPasteboard.generalPasteboard;
    59  		NSString *content = [p stringForType:NSPasteboardTypeString];
    60  		return (__bridge_retained CFTypeRef)content;
    61  	}
    62  }
    63  
    64  static CGFloat viewHeight(CFTypeRef viewRef) {
    65  	@autoreleasepool {
    66  		NSView *view = (__bridge NSView *)viewRef;
    67  		return [view bounds].size.height;
    68  	}
    69  }
    70  
    71  static CGFloat viewWidth(CFTypeRef viewRef) {
    72  	@autoreleasepool {
    73  		NSView *view = (__bridge NSView *)viewRef;
    74  		return [view bounds].size.width;
    75  	}
    76  }
    77  
    78  static CGFloat getScreenBackingScale(void) {
    79  	@autoreleasepool {
    80  		return [NSScreen.mainScreen backingScaleFactor];
    81  	}
    82  }
    83  
    84  static CGFloat getViewBackingScale(CFTypeRef viewRef) {
    85  	@autoreleasepool {
    86  		NSView *view = (__bridge NSView *)viewRef;
    87  		return [view.window backingScaleFactor];
    88  	}
    89  }
    90  
    91  static void setNeedsDisplay(CFTypeRef viewRef) {
    92  	@autoreleasepool {
    93  		NSView *view = (__bridge NSView *)viewRef;
    94  		[view setNeedsDisplay:YES];
    95  	}
    96  }
    97  
    98  static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
    99  	@autoreleasepool {
   100  		NSWindow *window = (__bridge NSWindow *)windowRef;
   101  		return [window cascadeTopLeftFromPoint:topLeft];
   102  	}
   103  }
   104  
   105  static void makeKeyAndOrderFront(CFTypeRef windowRef) {
   106  	@autoreleasepool {
   107  		NSWindow *window = (__bridge NSWindow *)windowRef;
   108  		[window makeKeyAndOrderFront:nil];
   109  	}
   110  }
   111  
   112  static void toggleFullScreen(CFTypeRef windowRef) {
   113  	@autoreleasepool {
   114  		NSWindow *window = (__bridge NSWindow *)windowRef;
   115  		[window toggleFullScreen:nil];
   116  	}
   117  }
   118  
   119  static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
   120  	@autoreleasepool {
   121  		NSWindow *window = (__bridge NSWindow *)windowRef;
   122  		return [window styleMask];
   123  	}
   124  }
   125  
   126  static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
   127  	@autoreleasepool {
   128  		NSWindow *window = (__bridge NSWindow *)windowRef;
   129  		window.styleMask = mask;
   130  	}
   131  }
   132  
   133  static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
   134  	@autoreleasepool {
   135  		NSWindow *window = (__bridge NSWindow *)windowRef;
   136  		window.titleVisibility = state;
   137  	}
   138  }
   139  
   140  static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
   141  	@autoreleasepool {
   142  		NSWindow *window = (__bridge NSWindow *)windowRef;
   143  		window.titlebarAppearsTransparent = (BOOL)transparent;
   144  	}
   145  }
   146  
   147  static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
   148  	@autoreleasepool {
   149  		NSWindow *window = (__bridge NSWindow *)windowRef;
   150  		[window standardWindowButton:btn].hidden = (BOOL)hide;
   151  	}
   152  }
   153  
   154  static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
   155  	@autoreleasepool {
   156  		NSWindow *window = (__bridge NSWindow *)windowRef;
   157  		[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
   158  	}
   159  }
   160  
   161  static void closeWindow(CFTypeRef windowRef) {
   162  	@autoreleasepool {
   163  		NSWindow* window = (__bridge NSWindow *)windowRef;
   164  		[window performClose:nil];
   165  	}
   166  }
   167  
   168  static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
   169  	@autoreleasepool {
   170  		NSWindow* window = (__bridge NSWindow *)windowRef;
   171  		NSSize size = NSMakeSize(width, height);
   172  		[window setContentSize:size];
   173  	}
   174  }
   175  
   176  static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
   177  	@autoreleasepool {
   178  		NSWindow* window = (__bridge NSWindow *)windowRef;
   179  		window.contentMinSize = NSMakeSize(width, height);
   180  	}
   181  }
   182  
   183  static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
   184  	@autoreleasepool {
   185  		NSWindow* window = (__bridge NSWindow *)windowRef;
   186  		window.contentMaxSize = NSMakeSize(width, height);
   187  	}
   188  }
   189  
   190  static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
   191  	@autoreleasepool {
   192  		NSWindow* window = (__bridge NSWindow *)windowRef;
   193  		NSRect r = NSMakeRect(x, y, w, h);
   194  		[window setFrame:r display:YES];
   195  	}
   196  }
   197  
   198  static void resetLayerFrame(CFTypeRef viewRef) {
   199  	@autoreleasepool {
   200  		NSView* view = (__bridge NSView *)viewRef;
   201  		NSRect r = view.frame;
   202  		view.layer.frame = r;
   203  	}
   204  }
   205  
   206  static void hideWindow(CFTypeRef windowRef) {
   207  	@autoreleasepool {
   208  		NSWindow* window = (__bridge NSWindow *)windowRef;
   209  		[window miniaturize:window];
   210  	}
   211  }
   212  
   213  static void unhideWindow(CFTypeRef windowRef) {
   214  	@autoreleasepool {
   215  		NSWindow* window = (__bridge NSWindow *)windowRef;
   216  		[window deminiaturize:window];
   217  	}
   218  }
   219  
   220  static NSRect getScreenFrame(CFTypeRef windowRef) {
   221  	@autoreleasepool {
   222  		NSWindow* window = (__bridge NSWindow *)windowRef;
   223  		return [[window screen] frame];
   224  	}
   225  }
   226  
   227  static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
   228  	@autoreleasepool {
   229  		NSWindow *window = (__bridge NSWindow *)windowRef;
   230  		window.title = (__bridge NSString *)titleRef;
   231  	}
   232  }
   233  
   234  static int isWindowZoomed(CFTypeRef windowRef) {
   235  	@autoreleasepool {
   236  		NSWindow *window = (__bridge NSWindow *)windowRef;
   237  		return window.zoomed ? 1 : 0;
   238  	}
   239  }
   240  
   241  static void zoomWindow(CFTypeRef windowRef) {
   242  	@autoreleasepool {
   243  		NSWindow *window = (__bridge NSWindow *)windowRef;
   244  		[window zoom:nil];
   245  	}
   246  }
   247  
   248  static CFTypeRef layerForView(CFTypeRef viewRef) {
   249  	@autoreleasepool {
   250  		NSView *view = (__bridge NSView *)viewRef;
   251  		return (__bridge CFTypeRef)view.layer;
   252  	}
   253  }
   254  
   255  static CFTypeRef windowForView(CFTypeRef viewRef) {
   256  	@autoreleasepool {
   257  		NSView *view = (__bridge NSView *)viewRef;
   258  		return (__bridge CFTypeRef)view.window;
   259  	}
   260  }
   261  
   262  static void raiseWindow(CFTypeRef windowRef) {
   263  	@autoreleasepool {
   264  		NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
   265  		if (![currentApp isActive]) {
   266  			[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
   267  		}
   268  		NSWindow* window = (__bridge NSWindow *)windowRef;
   269  		[window makeKeyAndOrderFront:nil];
   270  	}
   271  }
   272  
   273  static CFTypeRef createInputContext(CFTypeRef clientRef) {
   274  	@autoreleasepool {
   275  		id<NSTextInputClient> client = (__bridge id<NSTextInputClient>)clientRef;
   276  		NSTextInputContext *ctx = [[NSTextInputContext alloc] initWithClient:client];
   277  		return CFBridgingRetain(ctx);
   278  	}
   279  }
   280  
   281  static void discardMarkedText(CFTypeRef viewRef) {
   282  	@autoreleasepool {
   283  		id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
   284  		NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
   285  		if (view == [ctx client]) {
   286  			[ctx discardMarkedText];
   287  		}
   288  	}
   289  }
   290  
   291  static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
   292  	@autoreleasepool {
   293  		id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
   294  		NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
   295  		if (view == [ctx client]) {
   296  			[ctx invalidateCharacterCoordinates];
   297  		}
   298  	}
   299  }
   300  */
   301  import "C"
   302  
   303  func init() {
   304  	// Darwin requires that UI operations happen on the main thread only.
   305  	runtime.LockOSThread()
   306  }
   307  
   308  // AppKitViewEvent notifies the client of changes to the window AppKit handles.
   309  // The handles are retained until another AppKitViewEvent is sent.
   310  type AppKitViewEvent struct {
   311  	// View is a CFTypeRef for the NSView for the window.
   312  	View uintptr
   313  	// Layer is a CFTypeRef of the CALayer of View.
   314  	Layer uintptr
   315  }
   316  
   317  type window struct {
   318  	view        C.CFTypeRef
   319  	w           *callbacks
   320  	anim        bool
   321  	visible     bool
   322  	displayLink *displayLink
   323  	// redraw is a single entry channel for making sure only one
   324  	// display link redraw request is in flight.
   325  	redraw      chan struct{}
   326  	cursor      pointer.Cursor
   327  	pointerBtns pointer.Buttons
   328  	loop        *eventLoop
   329  	initialized bool
   330  
   331  	scale  float32
   332  	config Config
   333  }
   334  
   335  // launched is closed when applicationDidFinishLaunching is called.
   336  var launched = make(chan struct{})
   337  
   338  // nextTopLeft is the offset to use for the next window's call to
   339  // cascadeTopLeftFromPoint.
   340  var nextTopLeft C.NSPoint
   341  
   342  func windowFor(h C.uintptr_t) *window {
   343  	return cgo.Handle(h).Value().(*window)
   344  }
   345  
   346  func (w *window) contextView() C.CFTypeRef {
   347  	return w.view
   348  }
   349  
   350  func (w *window) ReadClipboard() {
   351  	cstr := C.readClipboard()
   352  	if cstr != 0 {
   353  		defer C.CFRelease(cstr)
   354  	}
   355  	content := nsstringToString(cstr)
   356  	w.ProcessEvent(transfer.DataEvent{
   357  		Type: "application/text",
   358  		Open: func() io.ReadCloser {
   359  			return io.NopCloser(strings.NewReader(content))
   360  		},
   361  	})
   362  }
   363  
   364  func (w *window) WriteClipboard(mime string, s []byte) {
   365  	cstr := stringToNSString(string(s))
   366  	defer C.CFRelease(cstr)
   367  	C.writeClipboard(cstr)
   368  }
   369  
   370  func (w *window) updateWindowMode() {
   371  	style := int(C.getWindowStyleMask(C.windowForView(w.view)))
   372  	if style&C.NSWindowStyleMaskFullScreen != 0 {
   373  		w.config.Mode = Fullscreen
   374  	} else {
   375  		w.config.Mode = Windowed
   376  	}
   377  	w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
   378  }
   379  
   380  func (w *window) Configure(options []Option) {
   381  	screenScale := float32(C.getScreenBackingScale())
   382  	cfg := configFor(screenScale)
   383  	prev := w.config
   384  	w.updateWindowMode()
   385  	cnf := w.config
   386  	cnf.apply(cfg, options)
   387  	window := C.windowForView(w.view)
   388  
   389  	switch cnf.Mode {
   390  	case Fullscreen:
   391  		switch prev.Mode {
   392  		case Fullscreen:
   393  		case Minimized:
   394  			C.unhideWindow(window)
   395  			fallthrough
   396  		default:
   397  			w.config.Mode = Fullscreen
   398  			C.toggleFullScreen(window)
   399  		}
   400  	case Minimized:
   401  		switch prev.Mode {
   402  		case Minimized, Fullscreen:
   403  		default:
   404  			w.config.Mode = Minimized
   405  			C.hideWindow(window)
   406  		}
   407  	case Maximized:
   408  		switch prev.Mode {
   409  		case Fullscreen:
   410  		case Minimized:
   411  			C.unhideWindow(window)
   412  			fallthrough
   413  		default:
   414  			w.config.Mode = Maximized
   415  			w.setTitle(prev, cnf)
   416  			if C.isWindowZoomed(window) == 0 {
   417  				C.zoomWindow(window)
   418  			}
   419  		}
   420  	case Windowed:
   421  		switch prev.Mode {
   422  		case Fullscreen:
   423  			C.toggleFullScreen(window)
   424  		case Minimized:
   425  			C.unhideWindow(window)
   426  		case Maximized:
   427  			if C.isWindowZoomed(window) != 0 {
   428  				C.zoomWindow(window)
   429  			}
   430  		}
   431  		w.config.Mode = Windowed
   432  		w.setTitle(prev, cnf)
   433  		if prev.Size != cnf.Size {
   434  			w.config.Size = cnf.Size
   435  			cnf.Size = cnf.Size.Div(int(screenScale))
   436  			C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
   437  		}
   438  		if prev.MinSize != cnf.MinSize {
   439  			w.config.MinSize = cnf.MinSize
   440  			cnf.MinSize = cnf.MinSize.Div(int(screenScale))
   441  			C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
   442  		}
   443  		if prev.MaxSize != cnf.MaxSize {
   444  			w.config.MaxSize = cnf.MaxSize
   445  			cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
   446  			C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
   447  		}
   448  	}
   449  	if cnf.Decorated != prev.Decorated {
   450  		w.config.Decorated = cnf.Decorated
   451  		mask := C.getWindowStyleMask(window)
   452  		style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
   453  		style = C.NSWindowStyleMaskFullSizeContentView
   454  		mask &^= style
   455  		barTrans := C.int(C.NO)
   456  		titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
   457  		if !cnf.Decorated {
   458  			mask |= style
   459  			barTrans = C.YES
   460  			titleVis = C.NSWindowTitleHidden
   461  		}
   462  		C.setWindowTitlebarAppearsTransparent(window, barTrans)
   463  		C.setWindowTitleVisibility(window, titleVis)
   464  		C.setWindowStyleMask(window, mask)
   465  		C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
   466  		C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
   467  		C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
   468  		// When toggling the titlebar, the layer doesn't update its frame
   469  		// until the next resize. Force it.
   470  		C.resetLayerFrame(w.view)
   471  	}
   472  	w.ProcessEvent(ConfigEvent{Config: w.config})
   473  }
   474  
   475  func (w *window) setTitle(prev, cnf Config) {
   476  	if prev.Title != cnf.Title {
   477  		w.config.Title = cnf.Title
   478  		title := stringToNSString(cnf.Title)
   479  		defer C.CFRelease(title)
   480  		C.setTitle(C.windowForView(w.view), title)
   481  	}
   482  }
   483  
   484  func (w *window) Perform(acts system.Action) {
   485  	window := C.windowForView(w.view)
   486  	walkActions(acts, func(a system.Action) {
   487  		switch a {
   488  		case system.ActionCenter:
   489  			r := C.getScreenFrame(window) // the screen size of the window
   490  			screenScale := float32(C.getScreenBackingScale())
   491  			sz := w.config.Size.Div(int(screenScale))
   492  			x := (int(r.size.width) - sz.X) / 2
   493  			y := (int(r.size.height) - sz.Y) / 2
   494  			C.setScreenFrame(window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
   495  		case system.ActionRaise:
   496  			C.raiseWindow(window)
   497  		}
   498  	})
   499  	if acts&system.ActionClose != 0 {
   500  		C.closeWindow(window)
   501  	}
   502  }
   503  
   504  func (w *window) SetCursor(cursor pointer.Cursor) {
   505  	w.cursor = windowSetCursor(w.cursor, cursor)
   506  }
   507  
   508  func (w *window) EditorStateChanged(old, new editorState) {
   509  	if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
   510  		C.discardMarkedText(w.view)
   511  		w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
   512  	}
   513  	if old.Selection.Caret != new.Selection.Caret || old.Selection.Transform != new.Selection.Transform {
   514  		C.invalidateCharacterCoordinates(w.view)
   515  	}
   516  }
   517  
   518  func (w *window) ShowTextInput(show bool) {}
   519  
   520  func (w *window) SetInputHint(_ key.InputHint) {}
   521  
   522  func (w *window) SetAnimating(anim bool) {
   523  	w.anim = anim
   524  	if w.anim && w.visible {
   525  		w.displayLink.Start()
   526  	} else {
   527  		w.displayLink.Stop()
   528  	}
   529  }
   530  
   531  func (w *window) runOnMain(f func()) {
   532  	runOnMain(func() {
   533  		// Make sure the view is still valid. The window might've been closed
   534  		// during the switch to the main thread.
   535  		if w.view != 0 {
   536  			f()
   537  		}
   538  	})
   539  }
   540  
   541  //export gio_onKeys
   542  func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
   543  	str := nsstringToString(cstr)
   544  	kmods := convertMods(mods)
   545  	ks := key.Release
   546  	if keyDown {
   547  		ks = key.Press
   548  	}
   549  	w := windowFor(h)
   550  	for _, k := range str {
   551  		if n, ok := convertKey(k); ok {
   552  			w.ProcessEvent(key.Event{
   553  				Name:      n,
   554  				Modifiers: kmods,
   555  				State:     ks,
   556  			})
   557  		}
   558  	}
   559  }
   560  
   561  //export gio_onText
   562  func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
   563  	str := nsstringToString(cstr)
   564  	w := windowFor(h)
   565  	w.w.EditorInsert(str)
   566  }
   567  
   568  //export gio_onMouse
   569  func gio_onMouse(h C.uintptr_t, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
   570  	w := windowFor(h)
   571  	t := time.Duration(float64(ti)*float64(time.Second) + .5)
   572  	xf, yf := float32(x)*w.scale, float32(y)*w.scale
   573  	dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
   574  	pos := f32.Point{X: xf, Y: yf}
   575  	var btn pointer.Buttons
   576  	switch cbtn {
   577  	case 0:
   578  		btn = pointer.ButtonPrimary
   579  	case 1:
   580  		btn = pointer.ButtonSecondary
   581  	case 2:
   582  		btn = pointer.ButtonTertiary
   583  	}
   584  	var typ pointer.Kind
   585  	switch cdir {
   586  	case C.MOUSE_MOVE:
   587  		typ = pointer.Move
   588  	case C.MOUSE_UP:
   589  		typ = pointer.Release
   590  		w.pointerBtns &^= btn
   591  	case C.MOUSE_DOWN:
   592  		typ = pointer.Press
   593  		w.pointerBtns |= btn
   594  		act, ok := w.w.ActionAt(pos)
   595  		if ok && w.config.Mode != Fullscreen {
   596  			switch act {
   597  			case system.ActionMove:
   598  				C.performWindowDragWithEvent(C.windowForView(w.view), evt)
   599  				return
   600  			}
   601  		}
   602  	case C.MOUSE_SCROLL:
   603  		typ = pointer.Scroll
   604  	default:
   605  		panic("invalid direction")
   606  	}
   607  	w.ProcessEvent(pointer.Event{
   608  		Kind:      typ,
   609  		Source:    pointer.Mouse,
   610  		Time:      t,
   611  		Buttons:   w.pointerBtns,
   612  		Position:  pos,
   613  		Scroll:    f32.Point{X: dxf, Y: dyf},
   614  		Modifiers: convertMods(mods),
   615  	})
   616  }
   617  
   618  //export gio_onDraw
   619  func gio_onDraw(h C.uintptr_t) {
   620  	w := windowFor(h)
   621  	w.draw()
   622  }
   623  
   624  //export gio_onFocus
   625  func gio_onFocus(h C.uintptr_t, focus C.int) {
   626  	w := windowFor(h)
   627  	w.SetCursor(w.cursor)
   628  	w.config.Focused = focus == 1
   629  	w.ProcessEvent(ConfigEvent{Config: w.config})
   630  }
   631  
   632  //export gio_onChangeScreen
   633  func gio_onChangeScreen(h C.uintptr_t, did uint64) {
   634  	w := windowFor(h)
   635  	w.displayLink.SetDisplayID(did)
   636  	C.setNeedsDisplay(w.view)
   637  }
   638  
   639  //export gio_hasMarkedText
   640  func gio_hasMarkedText(h C.uintptr_t) C.int {
   641  	w := windowFor(h)
   642  	state := w.w.EditorState()
   643  	if state.compose.Start != -1 {
   644  		return 1
   645  	}
   646  	return 0
   647  }
   648  
   649  //export gio_markedRange
   650  func gio_markedRange(h C.uintptr_t) C.NSRange {
   651  	w := windowFor(h)
   652  	state := w.w.EditorState()
   653  	rng := state.compose
   654  	start, end := rng.Start, rng.End
   655  	if start == -1 {
   656  		return C.NSMakeRange(C.NSNotFound, 0)
   657  	}
   658  	u16start := state.UTF16Index(start)
   659  	return C.NSMakeRange(
   660  		C.NSUInteger(u16start),
   661  		C.NSUInteger(state.UTF16Index(end)-u16start),
   662  	)
   663  }
   664  
   665  //export gio_selectedRange
   666  func gio_selectedRange(h C.uintptr_t) C.NSRange {
   667  	w := windowFor(h)
   668  	state := w.w.EditorState()
   669  	rng := state.Selection
   670  	start, end := rng.Start, rng.End
   671  	if start > end {
   672  		start, end = end, start
   673  	}
   674  	u16start := state.UTF16Index(start)
   675  	return C.NSMakeRange(
   676  		C.NSUInteger(u16start),
   677  		C.NSUInteger(state.UTF16Index(end)-u16start),
   678  	)
   679  }
   680  
   681  //export gio_unmarkText
   682  func gio_unmarkText(h C.uintptr_t) {
   683  	w := windowFor(h)
   684  	w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
   685  }
   686  
   687  //export gio_setMarkedText
   688  func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
   689  	w := windowFor(h)
   690  	str := nsstringToString(cstr)
   691  	state := w.w.EditorState()
   692  	rng := state.compose
   693  	if rng.Start == -1 {
   694  		rng = state.Selection.Range
   695  	}
   696  	if replaceRange.location != C.NSNotFound {
   697  		// replaceRange is relative to marked (or selected) text.
   698  		offset := state.UTF16Index(rng.Start)
   699  		start := state.RunesIndex(int(replaceRange.location) + offset)
   700  		end := state.RunesIndex(int(replaceRange.location+replaceRange.length) + offset)
   701  		rng = key.Range{
   702  			Start: start,
   703  			End:   end,
   704  		}
   705  	}
   706  	w.w.EditorReplace(rng, str)
   707  	comp := key.Range{
   708  		Start: rng.Start,
   709  		End:   rng.Start + utf8.RuneCountInString(str),
   710  	}
   711  	w.w.SetComposingRegion(comp)
   712  
   713  	sel := key.Range{Start: comp.End, End: comp.End}
   714  	if selRange.location != C.NSNotFound {
   715  		// selRange is relative to inserted text.
   716  		offset := state.UTF16Index(rng.Start)
   717  		start := state.RunesIndex(int(selRange.location) + offset)
   718  		end := state.RunesIndex(int(selRange.location+selRange.length) + offset)
   719  		sel = key.Range{
   720  			Start: start,
   721  			End:   end,
   722  		}
   723  	}
   724  	w.w.SetEditorSelection(sel)
   725  }
   726  
   727  //export gio_substringForProposedRange
   728  func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
   729  	w := windowFor(h)
   730  	state := w.w.EditorState()
   731  	start, end := state.Snippet.Start, state.Snippet.End
   732  	if start > end {
   733  		start, end = end, start
   734  	}
   735  	rng := key.Range{
   736  		Start: state.RunesIndex(int(crng.location)),
   737  		End:   state.RunesIndex(int(crng.location + crng.length)),
   738  	}
   739  	if rng.Start < start || end < rng.End {
   740  		w.w.SetEditorSnippet(rng)
   741  	}
   742  	u16start := state.UTF16Index(start)
   743  	actual.location = C.NSUInteger(u16start)
   744  	actual.length = C.NSUInteger(state.UTF16Index(end) - u16start)
   745  	return stringToNSString(state.Snippet.Text)
   746  }
   747  
   748  //export gio_insertText
   749  func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
   750  	w := windowFor(h)
   751  	state := w.w.EditorState()
   752  	rng := state.compose
   753  	if rng.Start == -1 {
   754  		rng = state.Selection.Range
   755  	}
   756  	if crng.location != C.NSNotFound {
   757  		rng = key.Range{
   758  			Start: state.RunesIndex(int(crng.location)),
   759  			End:   state.RunesIndex(int(crng.location + crng.length)),
   760  		}
   761  	}
   762  	str := nsstringToString(cstr)
   763  	w.w.EditorReplace(rng, str)
   764  	w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
   765  	start := rng.Start
   766  	if rng.End < start {
   767  		start = rng.End
   768  	}
   769  	pos := start + utf8.RuneCountInString(str)
   770  	w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
   771  }
   772  
   773  //export gio_characterIndexForPoint
   774  func gio_characterIndexForPoint(h C.uintptr_t, p C.NSPoint) C.NSUInteger {
   775  	return C.NSNotFound
   776  }
   777  
   778  //export gio_firstRectForCharacterRange
   779  func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
   780  	w := windowFor(h)
   781  	state := w.w.EditorState()
   782  	sel := state.Selection
   783  	u16start := state.UTF16Index(sel.Start)
   784  	actual.location = C.NSUInteger(u16start)
   785  	actual.length = 0
   786  	// Transform to NSView local coordinates (lower left origin, undo backing scale).
   787  	scale := 1. / float32(C.getViewBackingScale(w.view))
   788  	height := float32(C.viewHeight(w.view))
   789  	local := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height))
   790  	t := local.Mul(sel.Transform)
   791  	bounds := f32.Rectangle{
   792  		Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))),
   793  		Max: t.Transform(sel.Pos.Add(f32.Pt(0, sel.Descent))),
   794  	}.Canon()
   795  	sz := bounds.Size()
   796  	return C.NSMakeRect(
   797  		C.CGFloat(bounds.Min.X), C.CGFloat(bounds.Min.Y),
   798  		C.CGFloat(sz.X), C.CGFloat(sz.Y),
   799  	)
   800  }
   801  
   802  func (w *window) draw() {
   803  	select {
   804  	case <-w.redraw:
   805  	default:
   806  	}
   807  	w.visible = true
   808  	if w.anim {
   809  		w.SetAnimating(w.anim)
   810  	}
   811  	w.scale = float32(C.getViewBackingScale(w.view))
   812  	wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
   813  	sz := image.Point{
   814  		X: int(wf*w.scale + .5),
   815  		Y: int(hf*w.scale + .5),
   816  	}
   817  	if sz != w.config.Size {
   818  		w.config.Size = sz
   819  		w.ProcessEvent(ConfigEvent{Config: w.config})
   820  	}
   821  	if sz.X == 0 || sz.Y == 0 {
   822  		return
   823  	}
   824  	cfg := configFor(w.scale)
   825  	w.ProcessEvent(frameEvent{
   826  		FrameEvent: FrameEvent{
   827  			Now:    time.Now(),
   828  			Size:   w.config.Size,
   829  			Metric: cfg,
   830  		},
   831  		Sync: true,
   832  	})
   833  }
   834  
   835  func (w *window) ProcessEvent(e event.Event) {
   836  	w.w.ProcessEvent(e)
   837  	if w.initialized {
   838  		w.loop.FlushEvents()
   839  	}
   840  }
   841  
   842  func (w *window) Event() event.Event {
   843  	return w.loop.Event()
   844  }
   845  
   846  func (w *window) Invalidate() {
   847  	w.loop.Invalidate()
   848  }
   849  
   850  func (w *window) Run(f func()) {
   851  	w.loop.Run(f)
   852  }
   853  
   854  func (w *window) Frame(frame *op.Ops) {
   855  	w.loop.Frame(frame)
   856  }
   857  
   858  func configFor(scale float32) unit.Metric {
   859  	return unit.Metric{
   860  		PxPerDp: scale,
   861  		PxPerSp: scale,
   862  	}
   863  }
   864  
   865  //export gio_onAttached
   866  func gio_onAttached(h C.uintptr_t, attached C.int) {
   867  	w := windowFor(h)
   868  	if attached != 0 {
   869  		layer := C.layerForView(w.view)
   870  		w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
   871  	} else {
   872  		w.ProcessEvent(AppKitViewEvent{})
   873  		w.visible = false
   874  		w.SetAnimating(w.anim)
   875  	}
   876  }
   877  
   878  //export gio_onDestroy
   879  func gio_onDestroy(h C.uintptr_t) {
   880  	w := windowFor(h)
   881  	w.ProcessEvent(DestroyEvent{})
   882  	w.displayLink.Close()
   883  	w.displayLink = nil
   884  	cgo.Handle(h).Delete()
   885  	w.view = 0
   886  }
   887  
   888  //export gio_onHide
   889  func gio_onHide(h C.uintptr_t) {
   890  	w := windowFor(h)
   891  	w.visible = false
   892  	w.SetAnimating(w.anim)
   893  }
   894  
   895  //export gio_onShow
   896  func gio_onShow(h C.uintptr_t) {
   897  	w := windowFor(h)
   898  	w.draw()
   899  }
   900  
   901  //export gio_onFullscreen
   902  func gio_onFullscreen(h C.uintptr_t) {
   903  	w := windowFor(h)
   904  	w.config.Mode = Fullscreen
   905  	w.ProcessEvent(ConfigEvent{Config: w.config})
   906  }
   907  
   908  //export gio_onWindowed
   909  func gio_onWindowed(h C.uintptr_t) {
   910  	w := windowFor(h)
   911  	w.config.Mode = Windowed
   912  	w.ProcessEvent(ConfigEvent{Config: w.config})
   913  }
   914  
   915  //export gio_onFinishLaunching
   916  func gio_onFinishLaunching() {
   917  	close(launched)
   918  }
   919  
   920  func newWindow(win *callbacks, options []Option) {
   921  	<-launched
   922  	res := make(chan struct{})
   923  	runOnMain(func() {
   924  		w := &window{
   925  			redraw: make(chan struct{}, 1),
   926  			w:      win,
   927  		}
   928  		w.loop = newEventLoop(w.w, w.wakeup)
   929  		win.SetDriver(w)
   930  		if err := w.init(); err != nil {
   931  			w.ProcessEvent(DestroyEvent{Err: err})
   932  			return
   933  		}
   934  		window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
   935  		// Release our reference now that the NSWindow has it.
   936  		C.CFRelease(w.view)
   937  		w.updateWindowMode()
   938  		w.Configure(options)
   939  		if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
   940  			// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
   941  			// and just returns the offset we need for the first window.
   942  			nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
   943  		}
   944  		nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
   945  		// makeKeyAndOrderFront assumes ownership of our window reference.
   946  		C.makeKeyAndOrderFront(window)
   947  		w.initialized = true
   948  		res <- struct{}{}
   949  		w.loop.FlushEvents()
   950  	})
   951  	<-res
   952  }
   953  
   954  func (w *window) init() error {
   955  	view := C.gio_createView()
   956  	if view == 0 {
   957  		return errors.New("newOSWindow: failed to create view")
   958  	}
   959  	scale := float32(C.getViewBackingScale(view))
   960  	w.scale = scale
   961  	dl, err := newDisplayLink(func() {
   962  		select {
   963  		case w.redraw <- struct{}{}:
   964  		default:
   965  			return
   966  		}
   967  		w.runOnMain(func() {
   968  			if w.visible {
   969  				C.setNeedsDisplay(w.view)
   970  			}
   971  		})
   972  	})
   973  	w.displayLink = dl
   974  	if err != nil {
   975  		C.CFRelease(view)
   976  		return err
   977  	}
   978  	C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
   979  	w.view = view
   980  	return nil
   981  }
   982  
   983  func osMain() {
   984  	C.gio_main()
   985  }
   986  
   987  func convertKey(k rune) (key.Name, bool) {
   988  	var n key.Name
   989  	switch k {
   990  	case 0x1b:
   991  		n = key.NameEscape
   992  	case C.NSLeftArrowFunctionKey:
   993  		n = key.NameLeftArrow
   994  	case C.NSRightArrowFunctionKey:
   995  		n = key.NameRightArrow
   996  	case C.NSUpArrowFunctionKey:
   997  		n = key.NameUpArrow
   998  	case C.NSDownArrowFunctionKey:
   999  		n = key.NameDownArrow
  1000  	case 0xd:
  1001  		n = key.NameReturn
  1002  	case 0x3:
  1003  		n = key.NameEnter
  1004  	case C.NSHomeFunctionKey:
  1005  		n = key.NameHome
  1006  	case C.NSEndFunctionKey:
  1007  		n = key.NameEnd
  1008  	case 0x7f:
  1009  		n = key.NameDeleteBackward
  1010  	case C.NSDeleteFunctionKey:
  1011  		n = key.NameDeleteForward
  1012  	case C.NSPageUpFunctionKey:
  1013  		n = key.NamePageUp
  1014  	case C.NSPageDownFunctionKey:
  1015  		n = key.NamePageDown
  1016  	case C.NSF1FunctionKey:
  1017  		n = key.NameF1
  1018  	case C.NSF2FunctionKey:
  1019  		n = key.NameF2
  1020  	case C.NSF3FunctionKey:
  1021  		n = key.NameF3
  1022  	case C.NSF4FunctionKey:
  1023  		n = key.NameF4
  1024  	case C.NSF5FunctionKey:
  1025  		n = key.NameF5
  1026  	case C.NSF6FunctionKey:
  1027  		n = key.NameF6
  1028  	case C.NSF7FunctionKey:
  1029  		n = key.NameF7
  1030  	case C.NSF8FunctionKey:
  1031  		n = key.NameF8
  1032  	case C.NSF9FunctionKey:
  1033  		n = key.NameF9
  1034  	case C.NSF10FunctionKey:
  1035  		n = key.NameF10
  1036  	case C.NSF11FunctionKey:
  1037  		n = key.NameF11
  1038  	case C.NSF12FunctionKey:
  1039  		n = key.NameF12
  1040  	case 0x09, 0x19:
  1041  		n = key.NameTab
  1042  	case 0x20:
  1043  		n = key.NameSpace
  1044  	default:
  1045  		k = unicode.ToUpper(k)
  1046  		if !unicode.IsPrint(k) {
  1047  			return "", false
  1048  		}
  1049  		n = key.Name(k)
  1050  	}
  1051  	return n, true
  1052  }
  1053  
  1054  func convertMods(mods C.NSUInteger) key.Modifiers {
  1055  	var kmods key.Modifiers
  1056  	if mods&C.NSAlternateKeyMask != 0 {
  1057  		kmods |= key.ModAlt
  1058  	}
  1059  	if mods&C.NSControlKeyMask != 0 {
  1060  		kmods |= key.ModCtrl
  1061  	}
  1062  	if mods&C.NSCommandKeyMask != 0 {
  1063  		kmods |= key.ModCommand
  1064  	}
  1065  	if mods&C.NSShiftKeyMask != 0 {
  1066  		kmods |= key.ModShift
  1067  	}
  1068  	return kmods
  1069  }
  1070  
  1071  func (AppKitViewEvent) implementsViewEvent() {}
  1072  func (AppKitViewEvent) ImplementsEvent()     {}