Files
gio-patched/app/os_macos.go
T
Elias Naur 22cd88df9f all: rename the gioui.org/ui module to gioui.org
The "ui" is redundant and stutters.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-09-30 12:37:06 +02:00

333 lines
6.8 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package app
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -fmodules -fobjc-arc -x objective-c
#include <AppKit/AppKit.h>
#include "os_macos.h"
*/
import "C"
import (
"errors"
"image"
"runtime"
"sync"
"time"
"unsafe"
"gioui.org/f32"
"gioui.org/key"
"gioui.org/pointer"
)
func init() {
// Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread()
}
type window struct {
view C.CFTypeRef
w *Window
stage Stage
ppdp float32
scale float32
}
type viewCmd struct {
view C.CFTypeRef
f viewFunc
}
type viewFunc func(views viewMap, view C.CFTypeRef)
type viewMap map[C.CFTypeRef]*window
var (
viewOnce sync.Once
viewCmds = make(chan viewCmd)
viewAcks = make(chan struct{})
)
var mainWindow = newWindowRendezvous()
var viewFactory func() C.CFTypeRef
func viewDo(view C.CFTypeRef, f viewFunc) {
viewOnce.Do(func() {
go runViewCmdLoop()
})
viewCmds <- viewCmd{view, f}
<-viewAcks
}
func runViewCmdLoop() {
views := make(viewMap)
for {
select {
case cmd := <-viewCmds:
cmd.f(views, cmd.view)
viewAcks <- struct{}{}
}
}
}
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 Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.w.event(StageEvent{stage})
}
// Use a top level func for onFrameCallback to avoid
// garbage from viewDo.
func onFrameCmd(views viewMap, view C.CFTypeRef) {
// CVDisplayLink does not run on the main thread,
// so we have to ignore requests to windows being
// deleted.
if w, exists := views[view]; exists {
w.draw(false)
}
}
//export gio_onFrameCallback
func gio_onFrameCallback(view C.CFTypeRef) {
viewDo(view, onFrameCmd)
}
//export gio_onKeys
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
str := C.GoString(cstr)
var kmods key.Modifiers
if mods&C.NSEventModifierFlagCommand != 0 {
kmods |= key.ModCommand
}
if mods&C.NSEventModifierFlagShift != 0 {
kmods |= key.ModShift
}
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[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)
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.w.event(key.EditEvent{Text: str})
})
}
//export gio_onMouse
func gio_onMouse(view C.CFTypeRef, cdir C.int, x, y, dx, dy C.CGFloat, ti C.double) {
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")
}
t := time.Duration(float64(ti)*float64(time.Second) + .5)
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
x, y := float32(x)*w.scale, float32(y)*w.scale
dx, dy := float32(dx)*w.scale, float32(dy)*w.scale
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Time: t,
Position: f32.Point{X: x, Y: y},
Scroll: f32.Point{X: dx, Y: dy},
})
})
}
//export gio_onDraw
func gio_onDraw(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
if w, exists := views[view]; exists {
w.draw(true)
}
})
}
//export gio_onFocus
func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[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.ppdp, w.scale)
cfg.now = time.Now()
w.setStage(StageRunning)
w.w.event(UpdateEvent{
Size: image.Point{
X: width,
Y: height,
},
Config: cfg,
sync: sync,
})
}
func getPixelsPerDp(scale float32) float32 {
ppdp := float32(C.gio_getPixelsPerDP())
ppdp = ppdp * scale * monitorScale
if ppdp < minDensity {
ppdp = minDensity
}
return ppdp / scale
}
func configFor(ppdp, scale float32) Config {
ppdp = ppdp * scale
return Config{
pxPerDp: ppdp,
pxPerSp: ppdp,
}
}
//export gio_onTerminate
func gio_onTerminate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
delete(views, view)
w.w.event(DestroyEvent{})
})
}
//export gio_onHide
func gio_onHide(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.setStage(StagePaused)
})
}
//export gio_onShow
func gio_onShow(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.setStage(StageRunning)
})
}
//export gio_onCreate
func gio_onCreate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
scale := float32(C.gio_getBackingScale())
w := &window{
view: view,
ppdp: getPixelsPerDp(scale),
scale: scale,
}
wopts := <-mainWindow.out
w.w = wopts.window
w.w.setDriver(w)
views[view] = w
})
}
func createWindow(win *Window, opts *windowOptions) 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"))
}
scale := float32(C.gio_getBackingScale())
ppdp := getPixelsPerDp(scale)
cfg := configFor(ppdp, scale)
opts := wopts.opts
w := cfg.Px(opts.Width)
h := cfg.Px(opts.Height)
// Window sizes is on screen coordinates, not device pixels.
w = int(float32(w) / scale)
h = int(float32(h) / scale)
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) (rune, bool) {
if '0' <= k && k <= '9' || 'A' <= k && k <= 'Z' {
return k, true
}
if 'a' <= k && k <= 'z' {
return k - 0x20, true
}
var n rune
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 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
default:
return 0, false
}
return n, true
}