mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 23:55:39 +00:00
47c5859d87
We are going to encourage a model where pointers to a central (program global) Configs are passed to widgets at setup time, and not pass Configs at every frame. That way, the global Gonfig can change, but the pointers won't need updating. This change only switches the Draw event's Config pointer to a value to avoid tempting programs to use the event Config instead of updating their own central Configs. Signed-off-by: Elias Naur <mail@eliasnaur.com>
264 lines
5.0 KiB
Go
264 lines
5.0 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/ui"
|
|
"gioui.org/ui/f32"
|
|
"gioui.org/ui/key"
|
|
"gioui.org/ui/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
|
|
}
|
|
|
|
// Only support one main window for now.
|
|
var singleWindow struct {
|
|
mu sync.Mutex
|
|
hasOpts bool
|
|
opts *WindowOptions
|
|
}
|
|
|
|
var viewFactory func() C.CFTypeRef
|
|
|
|
var views = make(map[C.CFTypeRef]*window)
|
|
|
|
func (w *window) contextView() C.CFTypeRef {
|
|
return w.view
|
|
}
|
|
|
|
func (w *window) setTextInput(s key.TextInputState) {}
|
|
|
|
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(ChangeStage{stage})
|
|
}
|
|
|
|
//export gio_onFrameCallback
|
|
func gio_onFrameCallback(view C.CFTypeRef) {
|
|
w := views[view]
|
|
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)
|
|
var kmods key.Modifiers
|
|
if mods&C.NSEventModifierFlagCommand != 0 {
|
|
kmods |= key.ModCommand
|
|
}
|
|
if mods&C.NSEventModifierFlagShift != 0 {
|
|
kmods |= key.ModShift
|
|
}
|
|
w := views[view]
|
|
for _, k := range str {
|
|
if n, ok := convertKey(k); ok {
|
|
w.w.event(key.Chord{Name: n, Modifiers: kmods})
|
|
}
|
|
}
|
|
}
|
|
|
|
//export gio_onText
|
|
func gio_onText(view C.CFTypeRef, cstr *C.char) {
|
|
str := C.GoString(cstr)
|
|
w := views[view]
|
|
w.w.event(key.Edit{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)
|
|
w := views[view]
|
|
w.w.event(pointer.Event{
|
|
Type: typ,
|
|
Source: pointer.Mouse,
|
|
Time: t,
|
|
Position: f32.Point{X: float32(x), Y: float32(y)},
|
|
Scroll: f32.Point{X: float32(dx), Y: float32(dy)},
|
|
})
|
|
}
|
|
|
|
//export gio_onDraw
|
|
func gio_onDraw(view C.CFTypeRef) {
|
|
w := views[view]
|
|
w.draw(true)
|
|
}
|
|
|
|
//export gio_onFocus
|
|
func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
|
|
w := views[view]
|
|
w.w.event(key.Focus{Focus: focus == C.YES})
|
|
}
|
|
|
|
func (w *window) draw(sync bool) {
|
|
width, height := int(C.gio_viewWidth(w.view)+.5), int(C.gio_viewHeight(w.view)+.5)
|
|
if width == 0 || height == 0 {
|
|
return
|
|
}
|
|
cfg := getConfig()
|
|
cfg.Now = time.Now()
|
|
w.setStage(StageRunning)
|
|
w.w.event(Draw{
|
|
Size: image.Point{
|
|
X: width,
|
|
Y: height,
|
|
},
|
|
Config: cfg,
|
|
sync: sync,
|
|
})
|
|
}
|
|
|
|
func getConfig() ui.Config {
|
|
ppdp := float32(C.gio_getPixelsPerDP())
|
|
ppdp *= monitorScale
|
|
if ppdp < minDensity {
|
|
ppdp = minDensity
|
|
}
|
|
return ui.Config{
|
|
PxPerDp: ppdp,
|
|
PxPerSp: ppdp,
|
|
}
|
|
}
|
|
|
|
//export gio_onTerminate
|
|
func gio_onTerminate(view C.CFTypeRef) {
|
|
w := views[view]
|
|
delete(views, view)
|
|
w.setStage(StageDead)
|
|
close(windows)
|
|
}
|
|
|
|
//export gio_onHide
|
|
func gio_onHide(view C.CFTypeRef) {
|
|
w := views[view]
|
|
w.setStage(StagePaused)
|
|
}
|
|
|
|
//export gio_onShow
|
|
func gio_onShow(view C.CFTypeRef) {
|
|
w := views[view]
|
|
w.setStage(StageRunning)
|
|
}
|
|
|
|
//export gio_onCreate
|
|
func gio_onCreate(view C.CFTypeRef) {
|
|
w := &window{
|
|
view: view,
|
|
}
|
|
ow := newWindow(w)
|
|
w.w = ow
|
|
views[view] = w
|
|
windows <- ow
|
|
}
|
|
|
|
func createWindow(opts *WindowOptions) error {
|
|
singleWindow.mu.Lock()
|
|
defer singleWindow.mu.Unlock()
|
|
if singleWindow.hasOpts {
|
|
panic("only one window supported")
|
|
}
|
|
singleWindow.opts = opts
|
|
singleWindow.hasOpts = true
|
|
return nil
|
|
}
|
|
|
|
func Main() {
|
|
view := viewFactory()
|
|
if view == 0 {
|
|
// TODO: return this error from CreateWindow.
|
|
panic(errors.New("CreateWindow: failed to create view"))
|
|
}
|
|
cfg := getConfig()
|
|
opts := singleWindow.opts
|
|
w := cfg.Val(opts.Width)
|
|
h := cfg.Val(opts.Height)
|
|
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
|
|
}
|