gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/os_macos.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // +build darwin,!ios
     4  
     5  package app
     6  
     7  /*
     8  #cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -fmodules -fobjc-arc -x objective-c
     9  
    10  #include <AppKit/AppKit.h>
    11  #include "os_macos.h"
    12  */
    13  import "C"
    14  import (
    15  	"errors"
    16  	"image"
    17  	"runtime"
    18  	"sync"
    19  	"time"
    20  	"unsafe"
    21  
    22  	"gioui.org/ui/f32"
    23  	"gioui.org/ui/key"
    24  	"gioui.org/ui/pointer"
    25  )
    26  
    27  func init() {
    28  	// Darwin requires that UI operations happen on the main thread only.
    29  	runtime.LockOSThread()
    30  }
    31  
    32  type window struct {
    33  	view  C.CFTypeRef
    34  	w     *Window
    35  	stage Stage
    36  	ppdp  float32
    37  	scale float32
    38  }
    39  
    40  type viewCmd struct {
    41  	view C.CFTypeRef
    42  	f    viewFunc
    43  }
    44  
    45  type viewFunc func(views viewMap, view C.CFTypeRef)
    46  
    47  type viewMap map[C.CFTypeRef]*window
    48  
    49  var (
    50  	viewOnce sync.Once
    51  	viewCmds = make(chan viewCmd)
    52  	viewAcks = make(chan struct{})
    53  )
    54  
    55  var mainWindow = newWindowRendezvous()
    56  
    57  var viewFactory func() C.CFTypeRef
    58  
    59  func viewDo(view C.CFTypeRef, f viewFunc) {
    60  	viewOnce.Do(func() {
    61  		go runViewCmdLoop()
    62  	})
    63  	viewCmds <- viewCmd{view, f}
    64  	<-viewAcks
    65  }
    66  
    67  func runViewCmdLoop() {
    68  	views := make(viewMap)
    69  	for {
    70  		select {
    71  		case cmd := <-viewCmds:
    72  			cmd.f(views, cmd.view)
    73  			viewAcks <- struct{}{}
    74  		}
    75  	}
    76  }
    77  
    78  func (w *window) contextView() C.CFTypeRef {
    79  	return w.view
    80  }
    81  
    82  func (w *window) showTextInput(show bool) {}
    83  
    84  func (w *window) setAnimating(anim bool) {
    85  	var animb C.BOOL
    86  	if anim {
    87  		animb = 1
    88  	}
    89  	C.gio_setAnimating(w.view, animb)
    90  }
    91  
    92  func (w *window) setStage(stage Stage) {
    93  	if stage == w.stage {
    94  		return
    95  	}
    96  	w.stage = stage
    97  	w.w.event(StageEvent{stage})
    98  }
    99  
   100  // Use a top level func for onFrameCallback to avoid
   101  // garbage from viewDo.
   102  func onFrameCmd(views viewMap, view C.CFTypeRef) {
   103  	// CVDisplayLink does not run on the main thread,
   104  	// so we have to ignore requests to windows being
   105  	// deleted.
   106  	if w, exists := views[view]; exists {
   107  		w.draw(false)
   108  	}
   109  }
   110  
   111  //export gio_onFrameCallback
   112  func gio_onFrameCallback(view C.CFTypeRef) {
   113  	viewDo(view, onFrameCmd)
   114  }
   115  
   116  //export gio_onKeys
   117  func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
   118  	str := C.GoString(cstr)
   119  	var kmods key.Modifiers
   120  	if mods&C.NSEventModifierFlagCommand != 0 {
   121  		kmods |= key.ModCommand
   122  	}
   123  	if mods&C.NSEventModifierFlagShift != 0 {
   124  		kmods |= key.ModShift
   125  	}
   126  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   127  		w := views[view]
   128  		for _, k := range str {
   129  			if n, ok := convertKey(k); ok {
   130  				w.w.event(key.Event{Name: n, Modifiers: kmods})
   131  			}
   132  		}
   133  	})
   134  }
   135  
   136  //export gio_onText
   137  func gio_onText(view C.CFTypeRef, cstr *C.char) {
   138  	str := C.GoString(cstr)
   139  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   140  		w := views[view]
   141  		w.w.event(key.EditEvent{Text: str})
   142  	})
   143  }
   144  
   145  //export gio_onMouse
   146  func gio_onMouse(view C.CFTypeRef, cdir C.int, x, y, dx, dy C.CGFloat, ti C.double) {
   147  	var typ pointer.Type
   148  	switch cdir {
   149  	case C.GIO_MOUSE_MOVE:
   150  		typ = pointer.Move
   151  	case C.GIO_MOUSE_UP:
   152  		typ = pointer.Release
   153  	case C.GIO_MOUSE_DOWN:
   154  		typ = pointer.Press
   155  	default:
   156  		panic("invalid direction")
   157  	}
   158  	t := time.Duration(float64(ti)*float64(time.Second) + .5)
   159  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   160  		w := views[view]
   161  		x, y := float32(x)*w.scale, float32(y)*w.scale
   162  		dx, dy := float32(dx)*w.scale, float32(dy)*w.scale
   163  		w.w.event(pointer.Event{
   164  			Type:     typ,
   165  			Source:   pointer.Mouse,
   166  			Time:     t,
   167  			Position: f32.Point{X: x, Y: y},
   168  			Scroll:   f32.Point{X: dx, Y: dy},
   169  		})
   170  	})
   171  }
   172  
   173  //export gio_onDraw
   174  func gio_onDraw(view C.CFTypeRef) {
   175  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   176  		if w, exists := views[view]; exists {
   177  			w.draw(true)
   178  		}
   179  	})
   180  }
   181  
   182  //export gio_onFocus
   183  func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
   184  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   185  		w := views[view]
   186  		w.w.event(key.FocusEvent{Focus: focus == C.YES})
   187  	})
   188  }
   189  
   190  func (w *window) draw(sync bool) {
   191  	w.scale = float32(C.gio_getViewBackingScale(w.view))
   192  	wf, hf := float32(C.gio_viewWidth(w.view)), float32(C.gio_viewHeight(w.view))
   193  	if wf == 0 || hf == 0 {
   194  		return
   195  	}
   196  	width := int(wf*w.scale + .5)
   197  	height := int(hf*w.scale + .5)
   198  	cfg := configFor(w.ppdp, w.scale)
   199  	cfg.now = time.Now()
   200  	w.setStage(StageRunning)
   201  	w.w.event(UpdateEvent{
   202  		Size: image.Point{
   203  			X: width,
   204  			Y: height,
   205  		},
   206  		Config: cfg,
   207  		sync:   sync,
   208  	})
   209  }
   210  
   211  func getPixelsPerDp(scale float32) float32 {
   212  	ppdp := float32(C.gio_getPixelsPerDP())
   213  	ppdp = ppdp * scale * monitorScale
   214  	if ppdp < minDensity {
   215  		ppdp = minDensity
   216  	}
   217  	return ppdp / scale
   218  }
   219  
   220  func configFor(ppdp, scale float32) Config {
   221  	ppdp = ppdp * scale
   222  	return Config{
   223  		pxPerDp: ppdp,
   224  		pxPerSp: ppdp,
   225  	}
   226  }
   227  
   228  //export gio_onTerminate
   229  func gio_onTerminate(view C.CFTypeRef) {
   230  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   231  		w := views[view]
   232  		delete(views, view)
   233  		w.w.event(DestroyEvent{})
   234  	})
   235  }
   236  
   237  //export gio_onHide
   238  func gio_onHide(view C.CFTypeRef) {
   239  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   240  		w := views[view]
   241  		w.setStage(StagePaused)
   242  	})
   243  }
   244  
   245  //export gio_onShow
   246  func gio_onShow(view C.CFTypeRef) {
   247  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   248  		w := views[view]
   249  		w.setStage(StageRunning)
   250  	})
   251  }
   252  
   253  //export gio_onCreate
   254  func gio_onCreate(view C.CFTypeRef) {
   255  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   256  		scale := float32(C.gio_getBackingScale())
   257  		w := &window{
   258  			view:  view,
   259  			ppdp:  getPixelsPerDp(scale),
   260  			scale: scale,
   261  		}
   262  		wopts := <-mainWindow.out
   263  		w.w = wopts.window
   264  		w.w.setDriver(w)
   265  		views[view] = w
   266  	})
   267  }
   268  
   269  func createWindow(win *Window, opts *windowOptions) error {
   270  	mainWindow.in <- windowAndOptions{win, opts}
   271  	return <-mainWindow.errs
   272  }
   273  
   274  func main() {
   275  	wopts := <-mainWindow.out
   276  	view := viewFactory()
   277  	if view == 0 {
   278  		// TODO: return this error from CreateWindow.
   279  		panic(errors.New("CreateWindow: failed to create view"))
   280  	}
   281  	scale := float32(C.gio_getBackingScale())
   282  	ppdp := getPixelsPerDp(scale)
   283  	cfg := configFor(ppdp, scale)
   284  	opts := wopts.opts
   285  	w := cfg.Px(opts.Width)
   286  	h := cfg.Px(opts.Height)
   287  	// Window sizes is on screen coordinates, not device pixels.
   288  	w = int(float32(w) / scale)
   289  	h = int(float32(h) / scale)
   290  	title := C.CString(opts.Title)
   291  	defer C.free(unsafe.Pointer(title))
   292  	C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
   293  }
   294  
   295  func convertKey(k rune) (rune, bool) {
   296  	if '0' <= k && k <= '9' || 'A' <= k && k <= 'Z' {
   297  		return k, true
   298  	}
   299  	if 'a' <= k && k <= 'z' {
   300  		return k - 0x20, true
   301  	}
   302  	var n rune
   303  	switch k {
   304  	case 0x1b:
   305  		n = key.NameEscape
   306  	case C.NSLeftArrowFunctionKey:
   307  		n = key.NameLeftArrow
   308  	case C.NSRightArrowFunctionKey:
   309  		n = key.NameRightArrow
   310  	case C.NSUpArrowFunctionKey:
   311  		n = key.NameUpArrow
   312  	case C.NSDownArrowFunctionKey:
   313  		n = key.NameDownArrow
   314  	case 0xd:
   315  		n = key.NameReturn
   316  	case C.NSHomeFunctionKey:
   317  		n = key.NameHome
   318  	case C.NSEndFunctionKey:
   319  		n = key.NameEnd
   320  	case 0x7f:
   321  		n = key.NameDeleteBackward
   322  	case C.NSDeleteFunctionKey:
   323  		n = key.NameDeleteForward
   324  	case C.NSPageUpFunctionKey:
   325  		n = key.NamePageUp
   326  	case C.NSPageDownFunctionKey:
   327  		n = key.NamePageDown
   328  	default:
   329  		return 0, false
   330  	}
   331  	return n, true
   332  }