gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/os_ios.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 //go:build darwin && ios 4 // +build darwin,ios 5 6 package app 7 8 /* 9 #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c 10 11 #include <CoreGraphics/CoreGraphics.h> 12 #include <UIKit/UIKit.h> 13 #include <stdint.h> 14 15 __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle); 16 17 struct drawParams { 18 CGFloat dpi, sdpi; 19 CGFloat width, height; 20 CGFloat top, right, bottom, left; 21 }; 22 23 static void writeClipboard(unichar *chars, NSUInteger length) { 24 #if !TARGET_OS_TV 25 @autoreleasepool { 26 NSString *s = [NSString string]; 27 if (length > 0) { 28 s = [NSString stringWithCharacters:chars length:length]; 29 } 30 UIPasteboard *p = UIPasteboard.generalPasteboard; 31 p.string = s; 32 } 33 #endif 34 } 35 36 static CFTypeRef readClipboard(void) { 37 #if !TARGET_OS_TV 38 @autoreleasepool { 39 UIPasteboard *p = UIPasteboard.generalPasteboard; 40 return (__bridge_retained CFTypeRef)p.string; 41 } 42 #else 43 return nil; 44 #endif 45 } 46 47 static void showTextInput(CFTypeRef viewRef) { 48 UIView *view = (__bridge UIView *)viewRef; 49 [view becomeFirstResponder]; 50 } 51 52 static void hideTextInput(CFTypeRef viewRef) { 53 UIView *view = (__bridge UIView *)viewRef; 54 [view resignFirstResponder]; 55 } 56 57 static struct drawParams viewDrawParams(CFTypeRef viewRef) { 58 UIView *v = (__bridge UIView *)viewRef; 59 struct drawParams params; 60 CGFloat scale = v.layer.contentsScale; 61 // Use 163 as the standard ppi on iOS. 62 params.dpi = 163*scale; 63 params.sdpi = params.dpi; 64 UIEdgeInsets insets = v.layoutMargins; 65 if (@available(iOS 11.0, tvOS 11.0, *)) { 66 UIFontMetrics *metrics = [UIFontMetrics defaultMetrics]; 67 params.sdpi = [metrics scaledValueForValue:params.sdpi]; 68 insets = v.safeAreaInsets; 69 } 70 params.width = v.bounds.size.width*scale; 71 params.height = v.bounds.size.height*scale; 72 params.top = insets.top*scale; 73 params.right = insets.right*scale; 74 params.bottom = insets.bottom*scale; 75 params.left = insets.left*scale; 76 return params; 77 } 78 */ 79 import "C" 80 81 import ( 82 "image" 83 "io" 84 "runtime" 85 "runtime/cgo" 86 "runtime/debug" 87 "strings" 88 "time" 89 "unicode/utf16" 90 "unsafe" 91 92 "gioui.org/f32" 93 "gioui.org/io/event" 94 "gioui.org/io/key" 95 "gioui.org/io/pointer" 96 "gioui.org/io/system" 97 "gioui.org/io/transfer" 98 "gioui.org/op" 99 "gioui.org/unit" 100 ) 101 102 type UIKitViewEvent struct { 103 // ViewController is a CFTypeRef for the UIViewController backing a Window. 104 ViewController uintptr 105 } 106 107 type window struct { 108 view C.CFTypeRef 109 w *callbacks 110 displayLink *displayLink 111 loop *eventLoop 112 113 hidden bool 114 cursor pointer.Cursor 115 config Config 116 117 pointerMap []C.CFTypeRef 118 } 119 120 var mainWindow = newWindowRendezvous() 121 122 func init() { 123 // Darwin requires UI operations happen on the main thread only. 124 runtime.LockOSThread() 125 } 126 127 //export onCreate 128 func onCreate(view, controller C.CFTypeRef) { 129 wopts := <-mainWindow.out 130 w := &window{ 131 view: view, 132 w: wopts.window, 133 } 134 w.loop = newEventLoop(w.w, w.wakeup) 135 w.w.SetDriver(w) 136 mainWindow.windows <- struct{}{} 137 dl, err := newDisplayLink(func() { 138 w.draw(false) 139 }) 140 if err != nil { 141 w.w.ProcessEvent(DestroyEvent{Err: err}) 142 return 143 } 144 w.displayLink = dl 145 C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w))) 146 w.Configure(wopts.options) 147 w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)}) 148 } 149 150 func viewFor(h C.uintptr_t) *window { 151 return cgo.Handle(h).Value().(*window) 152 } 153 154 //export gio_onDraw 155 func gio_onDraw(h C.uintptr_t) { 156 w := viewFor(h) 157 w.draw(true) 158 } 159 160 func (w *window) draw(sync bool) { 161 if w.hidden { 162 return 163 } 164 params := C.viewDrawParams(w.view) 165 if params.width == 0 || params.height == 0 { 166 return 167 } 168 const inchPrDp = 1.0 / 163 169 m := unit.Metric{ 170 PxPerDp: float32(params.dpi) * inchPrDp, 171 PxPerSp: float32(params.sdpi) * inchPrDp, 172 } 173 dppp := unit.Dp(1. / m.PxPerDp) 174 w.ProcessEvent(frameEvent{ 175 FrameEvent: FrameEvent{ 176 Now: time.Now(), 177 Size: image.Point{ 178 X: int(params.width + .5), 179 Y: int(params.height + .5), 180 }, 181 Insets: Insets{ 182 Top: unit.Dp(params.top) * dppp, 183 Bottom: unit.Dp(params.bottom) * dppp, 184 Left: unit.Dp(params.left) * dppp, 185 Right: unit.Dp(params.right) * dppp, 186 }, 187 Metric: m, 188 }, 189 Sync: sync, 190 }) 191 } 192 193 //export onStop 194 func onStop(h C.uintptr_t) { 195 w := viewFor(h) 196 w.hidden = true 197 } 198 199 //export onStart 200 func onStart(h C.uintptr_t) { 201 w := viewFor(h) 202 w.hidden = false 203 w.draw(true) 204 } 205 206 //export onDestroy 207 func onDestroy(h C.uintptr_t) { 208 w := viewFor(h) 209 w.ProcessEvent(UIKitViewEvent{}) 210 w.ProcessEvent(DestroyEvent{}) 211 w.displayLink.Close() 212 w.displayLink = nil 213 cgo.Handle(h).Delete() 214 w.view = 0 215 } 216 217 //export onFocus 218 func onFocus(h C.uintptr_t, focus int) { 219 w := viewFor(h) 220 w.config.Focused = focus != 0 221 w.ProcessEvent(ConfigEvent{Config: w.config}) 222 } 223 224 //export onLowMemory 225 func onLowMemory() { 226 runtime.GC() 227 debug.FreeOSMemory() 228 } 229 230 //export onUpArrow 231 func onUpArrow(h C.uintptr_t) { 232 viewFor(h).onKeyCommand(key.NameUpArrow) 233 } 234 235 //export onDownArrow 236 func onDownArrow(h C.uintptr_t) { 237 viewFor(h).onKeyCommand(key.NameDownArrow) 238 } 239 240 //export onLeftArrow 241 func onLeftArrow(h C.uintptr_t) { 242 viewFor(h).onKeyCommand(key.NameLeftArrow) 243 } 244 245 //export onRightArrow 246 func onRightArrow(h C.uintptr_t) { 247 viewFor(h).onKeyCommand(key.NameRightArrow) 248 } 249 250 //export onDeleteBackward 251 func onDeleteBackward(h C.uintptr_t) { 252 viewFor(h).onKeyCommand(key.NameDeleteBackward) 253 } 254 255 //export onText 256 func onText(h C.uintptr_t, str C.CFTypeRef) { 257 w := viewFor(h) 258 w.w.EditorInsert(nsstringToString(str)) 259 } 260 261 //export onTouch 262 func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) { 263 var kind pointer.Kind 264 switch phase { 265 case C.UITouchPhaseBegan: 266 kind = pointer.Press 267 case C.UITouchPhaseMoved: 268 kind = pointer.Move 269 case C.UITouchPhaseEnded: 270 kind = pointer.Release 271 case C.UITouchPhaseCancelled: 272 kind = pointer.Cancel 273 default: 274 return 275 } 276 w := viewFor(h) 277 t := time.Duration(float64(ti) * float64(time.Second)) 278 p := f32.Point{X: float32(x), Y: float32(y)} 279 w.ProcessEvent(pointer.Event{ 280 Kind: kind, 281 Source: pointer.Touch, 282 PointerID: w.lookupTouch(last != 0, touchRef), 283 Position: p, 284 Time: t, 285 }) 286 } 287 288 func (w *window) ReadClipboard() { 289 cstr := C.readClipboard() 290 defer C.CFRelease(cstr) 291 content := nsstringToString(cstr) 292 w.ProcessEvent(transfer.DataEvent{ 293 Type: "application/text", 294 Open: func() io.ReadCloser { 295 return io.NopCloser(strings.NewReader(content)) 296 }, 297 }) 298 } 299 300 func (w *window) WriteClipboard(mime string, s []byte) { 301 u16 := utf16.Encode([]rune(string(s))) 302 var chars *C.unichar 303 if len(u16) > 0 { 304 chars = (*C.unichar)(unsafe.Pointer(&u16[0])) 305 } 306 C.writeClipboard(chars, C.NSUInteger(len(u16))) 307 } 308 309 func (w *window) Configure([]Option) { 310 // Decorations are never disabled. 311 w.config.Decorated = true 312 w.ProcessEvent(ConfigEvent{Config: w.config}) 313 } 314 315 func (w *window) EditorStateChanged(old, new editorState) {} 316 317 func (w *window) Perform(system.Action) {} 318 319 func (w *window) SetAnimating(anim bool) { 320 if anim { 321 w.displayLink.Start() 322 } else { 323 w.displayLink.Stop() 324 } 325 } 326 327 func (w *window) SetCursor(cursor pointer.Cursor) { 328 w.cursor = windowSetCursor(w.cursor, cursor) 329 } 330 331 func (w *window) onKeyCommand(name key.Name) { 332 w.ProcessEvent(key.Event{ 333 Name: name, 334 }) 335 } 336 337 // lookupTouch maps an UITouch pointer value to an index. If 338 // last is set, the map is cleared. 339 func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID { 340 id := -1 341 for i, ref := range w.pointerMap { 342 if ref == touch { 343 id = i 344 break 345 } 346 } 347 if id == -1 { 348 id = len(w.pointerMap) 349 w.pointerMap = append(w.pointerMap, touch) 350 } 351 if last { 352 w.pointerMap = w.pointerMap[:0] 353 } 354 return pointer.ID(id) 355 } 356 357 func (w *window) contextView() C.CFTypeRef { 358 return w.view 359 } 360 361 func (w *window) ShowTextInput(show bool) { 362 if show { 363 C.showTextInput(w.view) 364 } else { 365 C.hideTextInput(w.view) 366 } 367 } 368 369 func (w *window) SetInputHint(_ key.InputHint) {} 370 371 func (w *window) ProcessEvent(e event.Event) { 372 w.w.ProcessEvent(e) 373 w.loop.FlushEvents() 374 } 375 376 func (w *window) Event() event.Event { 377 return w.loop.Event() 378 } 379 380 func (w *window) Invalidate() { 381 w.loop.Invalidate() 382 } 383 384 func (w *window) Run(f func()) { 385 w.loop.Run(f) 386 } 387 388 func (w *window) Frame(frame *op.Ops) { 389 w.loop.Frame(frame) 390 } 391 392 func newWindow(win *callbacks, options []Option) { 393 mainWindow.in <- windowAndConfig{win, options} 394 <-mainWindow.windows 395 } 396 397 func osMain() { 398 } 399 400 //export gio_runMain 401 func gio_runMain() { 402 runMain() 403 } 404 405 func (UIKitViewEvent) implementsViewEvent() {} 406 func (UIKitViewEvent) ImplementsEvent() {}