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