github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/internal/window/os_macos.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // +build darwin,!ios
     4  
     5  package window
     6  
     7  import (
     8  	"errors"
     9  	"image"
    10  	"runtime"
    11  	"sync"
    12  	"time"
    13  	"unicode"
    14  	"unsafe"
    15  
    16  	"github.com/gop9/olt/gio/f32"
    17  	"github.com/gop9/olt/gio/io/key"
    18  	"github.com/gop9/olt/gio/io/pointer"
    19  	"github.com/gop9/olt/gio/io/system"
    20  )
    21  
    22  /*
    23  #cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
    24  
    25  
    26  #include <AppKit/AppKit.h>
    27  #include "os_macos.h"
    28  */
    29  import "C"
    30  
    31  func init() {
    32  	// Darwin requires that UI operations happen on the main thread only.
    33  	runtime.LockOSThread()
    34  }
    35  
    36  type window struct {
    37  	view  C.CFTypeRef
    38  	w     Callbacks
    39  	stage system.Stage
    40  	scale float32
    41  }
    42  
    43  type viewCmd struct {
    44  	view C.CFTypeRef
    45  	f    viewFunc
    46  }
    47  
    48  type viewFunc func(views viewMap, view C.CFTypeRef)
    49  
    50  type viewMap map[C.CFTypeRef]*window
    51  
    52  var (
    53  	viewOnce sync.Once
    54  	viewCmds = make(chan viewCmd)
    55  	viewAcks = make(chan struct{})
    56  )
    57  
    58  var mainWindow = newWindowRendezvous()
    59  
    60  var viewFactory func() C.CFTypeRef
    61  
    62  func viewDo(view C.CFTypeRef, f viewFunc) {
    63  	viewOnce.Do(func() {
    64  		go runViewCmdLoop()
    65  	})
    66  	viewCmds <- viewCmd{view, f}
    67  	<-viewAcks
    68  }
    69  
    70  func runViewCmdLoop() {
    71  	views := make(viewMap)
    72  	for {
    73  		select {
    74  		case cmd := <-viewCmds:
    75  			cmd.f(views, cmd.view)
    76  			viewAcks <- struct{}{}
    77  		}
    78  	}
    79  }
    80  
    81  func (w *window) contextView() C.CFTypeRef {
    82  	return w.view
    83  }
    84  
    85  func (w *window) ShowTextInput(show bool) {}
    86  
    87  func (w *window) SetAnimating(anim bool) {
    88  	var animb C.BOOL
    89  	if anim {
    90  		animb = 1
    91  	}
    92  	C.gio_setAnimating(w.view, animb)
    93  }
    94  
    95  func (w *window) setStage(stage system.Stage) {
    96  	if stage == w.stage {
    97  		return
    98  	}
    99  	w.stage = stage
   100  	w.w.Event(system.StageEvent{Stage: stage})
   101  }
   102  
   103  // Use a top level func for onFrameCallback to avoid
   104  // garbage from viewDo.
   105  func onFrameCmd(views viewMap, view C.CFTypeRef) {
   106  	// CVDisplayLink does not run on the main thread,
   107  	// so we have to ignore requests to windows being
   108  	// deleted.
   109  	if w, exists := views[view]; exists {
   110  		w.draw(false)
   111  	}
   112  }
   113  
   114  //export gio_onFrameCallback
   115  func gio_onFrameCallback(view C.CFTypeRef) {
   116  	viewDo(view, onFrameCmd)
   117  }
   118  
   119  //export gio_onKeys
   120  func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
   121  	str := C.GoString(cstr)
   122  	kmods := convertMods(mods)
   123  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   124  		w := views[view]
   125  		for _, k := range str {
   126  			if n, ok := convertKey(k); ok {
   127  				w.w.Event(key.Event{
   128  					Name:      n,
   129  					Modifiers: kmods,
   130  				})
   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, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
   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  	var btns pointer.Buttons
   159  	if cbtns&(1<<0) != 0 {
   160  		btns |= pointer.ButtonLeft
   161  	}
   162  	if cbtns&(1<<1) != 0 {
   163  		btns |= pointer.ButtonRight
   164  	}
   165  	if cbtns&(1<<2) != 0 {
   166  		btns |= pointer.ButtonMiddle
   167  	}
   168  	t := time.Duration(float64(ti)*float64(time.Second) + .5)
   169  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   170  		w := views[view]
   171  		x, y := float32(x)*w.scale, float32(y)*w.scale
   172  		dx, dy := float32(dx)*w.scale, float32(dy)*w.scale
   173  		w.w.Event(pointer.Event{
   174  			Type:      typ,
   175  			Source:    pointer.Mouse,
   176  			Time:      t,
   177  			Buttons:   btns,
   178  			Position:  f32.Point{X: x, Y: y},
   179  			Scroll:    f32.Point{X: dx, Y: dy},
   180  			Modifiers: convertMods(mods),
   181  		})
   182  	})
   183  }
   184  
   185  //export gio_onDraw
   186  func gio_onDraw(view C.CFTypeRef) {
   187  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   188  		if w, exists := views[view]; exists {
   189  			w.draw(true)
   190  		}
   191  	})
   192  }
   193  
   194  //export gio_onFocus
   195  func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
   196  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   197  		w := views[view]
   198  		w.w.Event(key.FocusEvent{Focus: focus == C.YES})
   199  	})
   200  }
   201  
   202  func (w *window) draw(sync bool) {
   203  	w.scale = float32(C.gio_getViewBackingScale(w.view))
   204  	wf, hf := float32(C.gio_viewWidth(w.view)), float32(C.gio_viewHeight(w.view))
   205  	if wf == 0 || hf == 0 {
   206  		return
   207  	}
   208  	width := int(wf*w.scale + .5)
   209  	height := int(hf*w.scale + .5)
   210  	cfg := configFor(w.scale)
   211  	cfg.now = time.Now()
   212  	w.setStage(system.StageRunning)
   213  	w.w.Event(FrameEvent{
   214  		FrameEvent: system.FrameEvent{
   215  			Size: image.Point{
   216  				X: width,
   217  				Y: height,
   218  			},
   219  			Config: &cfg,
   220  		},
   221  		Sync: sync,
   222  	})
   223  }
   224  
   225  func configFor(scale float32) config {
   226  	return config{
   227  		pxPerDp: scale,
   228  		pxPerSp: scale,
   229  	}
   230  }
   231  
   232  //export gio_onTerminate
   233  func gio_onTerminate(view C.CFTypeRef) {
   234  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   235  		w := views[view]
   236  		delete(views, view)
   237  		w.w.Event(system.DestroyEvent{})
   238  	})
   239  }
   240  
   241  //export gio_onHide
   242  func gio_onHide(view C.CFTypeRef) {
   243  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   244  		w := views[view]
   245  		w.setStage(system.StagePaused)
   246  	})
   247  }
   248  
   249  //export gio_onShow
   250  func gio_onShow(view C.CFTypeRef) {
   251  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   252  		w := views[view]
   253  		w.setStage(system.StageRunning)
   254  	})
   255  }
   256  
   257  //export gio_onCreate
   258  func gio_onCreate(view C.CFTypeRef) {
   259  	viewDo(view, func(views viewMap, view C.CFTypeRef) {
   260  		scale := float32(C.gio_getViewBackingScale(view))
   261  		w := &window{
   262  			view:  view,
   263  			scale: scale,
   264  		}
   265  		wopts := <-mainWindow.out
   266  		w.w = wopts.window
   267  		w.w.SetDriver(w)
   268  		views[view] = w
   269  	})
   270  }
   271  
   272  func NewWindow(win Callbacks, opts *Options) error {
   273  	mainWindow.in <- windowAndOptions{win, opts}
   274  	return <-mainWindow.errs
   275  }
   276  
   277  func Main() {
   278  	wopts := <-mainWindow.out
   279  	view := viewFactory()
   280  	if view == 0 {
   281  		// TODO: return this error from CreateWindow.
   282  		panic(errors.New("CreateWindow: failed to create view"))
   283  	}
   284  	// Window sizes is in unscaled screen coordinates, not device pixels.
   285  	cfg := configFor(1.0)
   286  	opts := wopts.opts
   287  	w := cfg.Px(opts.Width)
   288  	h := cfg.Px(opts.Height)
   289  	w = int(float32(w))
   290  	h = int(float32(h))
   291  	title := C.CString(opts.Title)
   292  	defer C.free(unsafe.Pointer(title))
   293  	C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
   294  }
   295  
   296  func convertKey(k rune) (string, bool) {
   297  	var n string
   298  	switch k {
   299  	case 0x1b:
   300  		n = key.NameEscape
   301  	case C.NSLeftArrowFunctionKey:
   302  		n = key.NameLeftArrow
   303  	case C.NSRightArrowFunctionKey:
   304  		n = key.NameRightArrow
   305  	case C.NSUpArrowFunctionKey:
   306  		n = key.NameUpArrow
   307  	case C.NSDownArrowFunctionKey:
   308  		n = key.NameDownArrow
   309  	case 0xd:
   310  		n = key.NameReturn
   311  	case 0x3:
   312  		n = key.NameEnter
   313  	case C.NSHomeFunctionKey:
   314  		n = key.NameHome
   315  	case C.NSEndFunctionKey:
   316  		n = key.NameEnd
   317  	case 0x7f:
   318  		n = key.NameDeleteBackward
   319  	case C.NSDeleteFunctionKey:
   320  		n = key.NameDeleteForward
   321  	case C.NSPageUpFunctionKey:
   322  		n = key.NamePageUp
   323  	case C.NSPageDownFunctionKey:
   324  		n = key.NamePageDown
   325  	case C.NSF1FunctionKey:
   326  		n = "F1"
   327  	case C.NSF2FunctionKey:
   328  		n = "F2"
   329  	case C.NSF3FunctionKey:
   330  		n = "F3"
   331  	case C.NSF4FunctionKey:
   332  		n = "F4"
   333  	case C.NSF5FunctionKey:
   334  		n = "F5"
   335  	case C.NSF6FunctionKey:
   336  		n = "F6"
   337  	case C.NSF7FunctionKey:
   338  		n = "F7"
   339  	case C.NSF8FunctionKey:
   340  		n = "F8"
   341  	case C.NSF9FunctionKey:
   342  		n = "F9"
   343  	case C.NSF10FunctionKey:
   344  		n = "F10"
   345  	case C.NSF11FunctionKey:
   346  		n = "F11"
   347  	case C.NSF12FunctionKey:
   348  		n = "F12"
   349  	case 0x09, 0x19:
   350  		n = key.NameTab
   351  	case 0x20:
   352  		n = "Space"
   353  	default:
   354  		k = unicode.ToUpper(k)
   355  		if !unicode.IsPrint(k) {
   356  			return "", false
   357  		}
   358  		n = string(k)
   359  	}
   360  	return n, true
   361  }
   362  
   363  func convertMods(mods C.NSUInteger) key.Modifiers {
   364  	var kmods key.Modifiers
   365  	if mods&C.NSAlternateKeyMask != 0 {
   366  		kmods |= key.ModAlt
   367  	}
   368  	if mods&C.NSControlKeyMask != 0 {
   369  		kmods |= key.ModCtrl
   370  	}
   371  	if mods&C.NSCommandKeyMask != 0 {
   372  		kmods |= key.ModCommand
   373  	}
   374  	if mods&C.NSShiftKeyMask != 0 {
   375  		kmods |= key.ModShift
   376  	}
   377  	return kmods
   378  }