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 }