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 }