Files
gio/app/internal/window/os_macos.go
T
Elias Naur a3dc2d6cdc app/internal/window: [macOS] run most callbacks on the main thread
Before this change, we had a viewDo that serialized a function on a view
on a single goroutine. For better or worse, most callbacks in Cocoa
happen on the main thread, so we might as well use that.

The only exception is the frame callback from the CADisplayLink.
We could force that through the main thread as well, but for
efficiency settle with making the view-to-window map synchronized.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2020-05-17 15:43:12 +02:00

349 lines
6.9 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package window
import (
"errors"
"image"
"runtime"
"sync"
"time"
"unicode"
"unsafe"
"gioui.org/f32"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
_ "gioui.org/app/internal/cocoainit"
)
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
#include <AppKit/AppKit.h>
#include "os_macos.h"
*/
import "C"
func init() {
// Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread()
}
type window struct {
view C.CFTypeRef
w Callbacks
stage system.Stage
scale float32
}
// viewMap is the mapping from Cocoa NSViews to Go windows.
var viewMap sync.Map
var mainWindow = newWindowRendezvous()
var viewFactory func() C.CFTypeRef
// mustView is like lookoupView, except that it panics
// if the view isn't mapped.
func mustView(view C.CFTypeRef) *window {
w, ok := lookupView(view)
if !ok {
panic("no window view view")
}
return w
}
func lookupView(view C.CFTypeRef) (*window, bool) {
w, exists := viewMap.Load(view)
if !exists {
return nil, false
}
return w.(*window), true
}
func deleteView(view C.CFTypeRef) {
viewMap.Delete(view)
}
func insertView(view C.CFTypeRef, w *window) {
viewMap.Store(view, w)
}
func (w *window) contextView() C.CFTypeRef {
return w.view
}
func (w *window) ShowTextInput(show bool) {}
func (w *window) SetAnimating(anim bool) {
var animb C.BOOL
if anim {
animb = 1
}
C.gio_setAnimating(w.view, animb)
}
func (w *window) setStage(stage system.Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.w.Event(system.StageEvent{Stage: stage})
}
//export gio_onFrameCallback
func gio_onFrameCallback(view C.CFTypeRef) {
w, exists := lookupView(view)
if exists {
w.draw(false)
}
}
//export gio_onKeys
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
str := C.GoString(cstr)
kmods := convertMods(mods)
w := mustView(view)
for _, k := range str {
if n, ok := convertKey(k); ok {
w.w.Event(key.Event{
Name: n,
Modifiers: kmods,
})
}
}
}
//export gio_onText
func gio_onText(view C.CFTypeRef, cstr *C.char) {
str := C.GoString(cstr)
w := mustView(view)
w.w.Event(key.EditEvent{Text: str})
}
//export gio_onMouse
func gio_onMouse(view C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
var typ pointer.Type
switch cdir {
case C.GIO_MOUSE_MOVE:
typ = pointer.Move
case C.GIO_MOUSE_UP:
typ = pointer.Release
case C.GIO_MOUSE_DOWN:
typ = pointer.Press
default:
panic("invalid direction")
}
var btns pointer.Buttons
if cbtns&(1<<0) != 0 {
btns |= pointer.ButtonLeft
}
if cbtns&(1<<1) != 0 {
btns |= pointer.ButtonRight
}
if cbtns&(1<<2) != 0 {
btns |= pointer.ButtonMiddle
}
t := time.Duration(float64(ti)*float64(time.Second) + .5)
w := mustView(view)
xf, yf := float32(x)*w.scale, float32(y)*w.scale
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
w.w.Event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Time: t,
Buttons: btns,
Position: f32.Point{X: xf, Y: yf},
Scroll: f32.Point{X: dxf, Y: dyf},
Modifiers: convertMods(mods),
})
}
//export gio_onDraw
func gio_onDraw(view C.CFTypeRef) {
w := mustView(view)
w.draw(true)
}
//export gio_onFocus
func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
w := mustView(view)
w.w.Event(key.FocusEvent{Focus: focus == C.YES})
}
func (w *window) draw(sync bool) {
w.scale = float32(C.gio_getViewBackingScale(w.view))
wf, hf := float32(C.gio_viewWidth(w.view)), float32(C.gio_viewHeight(w.view))
if wf == 0 || hf == 0 {
return
}
width := int(wf*w.scale + .5)
height := int(hf*w.scale + .5)
cfg := configFor(w.scale)
cfg.now = time.Now()
w.setStage(system.StageRunning)
w.w.Event(FrameEvent{
FrameEvent: system.FrameEvent{
Size: image.Point{
X: width,
Y: height,
},
Config: &cfg,
},
Sync: sync,
})
}
func configFor(scale float32) config {
return config{
pxPerDp: scale,
pxPerSp: scale,
}
}
//export gio_onTerminate
func gio_onTerminate(view C.CFTypeRef) {
w := mustView(view)
deleteView(view)
w.w.Event(system.DestroyEvent{})
}
//export gio_onHide
func gio_onHide(view C.CFTypeRef) {
w := mustView(view)
w.setStage(system.StagePaused)
}
//export gio_onShow
func gio_onShow(view C.CFTypeRef) {
w := mustView(view)
w.setStage(system.StageRunning)
}
//export gio_onCreate
func gio_onCreate(view C.CFTypeRef) {
scale := float32(C.gio_getViewBackingScale(view))
w := &window{
view: view,
scale: scale,
}
wopts := <-mainWindow.out
w.w = wopts.window
w.w.SetDriver(w)
insertView(view, w)
}
func NewWindow(win Callbacks, opts *Options) error {
mainWindow.in <- windowAndOptions{win, opts}
return <-mainWindow.errs
}
func Main() {
wopts := <-mainWindow.out
view := viewFactory()
if view == 0 {
// TODO: return this error from CreateWindow.
panic(errors.New("CreateWindow: failed to create view"))
}
// Window sizes is in unscaled screen coordinates, not device pixels.
cfg := configFor(1.0)
opts := wopts.opts
w := cfg.Px(opts.Width)
h := cfg.Px(opts.Height)
w = int(float32(w))
h = int(float32(h))
title := C.CString(opts.Title)
defer C.free(unsafe.Pointer(title))
C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
}
func convertKey(k rune) (string, bool) {
var n string
switch k {
case 0x1b:
n = key.NameEscape
case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow
case C.NSRightArrowFunctionKey:
n = key.NameRightArrow
case C.NSUpArrowFunctionKey:
n = key.NameUpArrow
case C.NSDownArrowFunctionKey:
n = key.NameDownArrow
case 0xd:
n = key.NameReturn
case 0x3:
n = key.NameEnter
case C.NSHomeFunctionKey:
n = key.NameHome
case C.NSEndFunctionKey:
n = key.NameEnd
case 0x7f:
n = key.NameDeleteBackward
case C.NSDeleteFunctionKey:
n = key.NameDeleteForward
case C.NSPageUpFunctionKey:
n = key.NamePageUp
case C.NSPageDownFunctionKey:
n = key.NamePageDown
case C.NSF1FunctionKey:
n = "F1"
case C.NSF2FunctionKey:
n = "F2"
case C.NSF3FunctionKey:
n = "F3"
case C.NSF4FunctionKey:
n = "F4"
case C.NSF5FunctionKey:
n = "F5"
case C.NSF6FunctionKey:
n = "F6"
case C.NSF7FunctionKey:
n = "F7"
case C.NSF8FunctionKey:
n = "F8"
case C.NSF9FunctionKey:
n = "F9"
case C.NSF10FunctionKey:
n = "F10"
case C.NSF11FunctionKey:
n = "F11"
case C.NSF12FunctionKey:
n = "F12"
case 0x09, 0x19:
n = key.NameTab
case 0x20:
n = "Space"
default:
k = unicode.ToUpper(k)
if !unicode.IsPrint(k) {
return "", false
}
n = string(k)
}
return n, true
}
func convertMods(mods C.NSUInteger) key.Modifiers {
var kmods key.Modifiers
if mods&C.NSAlternateKeyMask != 0 {
kmods |= key.ModAlt
}
if mods&C.NSControlKeyMask != 0 {
kmods |= key.ModCtrl
}
if mods&C.NSCommandKeyMask != 0 {
kmods |= key.ModCommand
}
if mods&C.NSShiftKeyMask != 0 {
kmods |= key.ModShift
}
return kmods
}