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