github.com/Seikaijyu/gio@v0.0.1/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 struct drawParams { 16 CGFloat dpi, sdpi; 17 CGFloat width, height; 18 CGFloat top, right, bottom, left; 19 }; 20 21 static void writeClipboard(unichar *chars, NSUInteger length) { 22 @autoreleasepool { 23 NSString *s = [NSString string]; 24 if (length > 0) { 25 s = [NSString stringWithCharacters:chars length:length]; 26 } 27 UIPasteboard *p = UIPasteboard.generalPasteboard; 28 p.string = s; 29 } 30 } 31 32 static CFTypeRef readClipboard(void) { 33 @autoreleasepool { 34 UIPasteboard *p = UIPasteboard.generalPasteboard; 35 return (__bridge_retained CFTypeRef)p.string; 36 } 37 } 38 39 static void showTextInput(CFTypeRef viewRef) { 40 UIView *view = (__bridge UIView *)viewRef; 41 [view becomeFirstResponder]; 42 } 43 44 static void hideTextInput(CFTypeRef viewRef) { 45 UIView *view = (__bridge UIView *)viewRef; 46 [view resignFirstResponder]; 47 } 48 49 static struct drawParams viewDrawParams(CFTypeRef viewRef) { 50 UIView *v = (__bridge UIView *)viewRef; 51 struct drawParams params; 52 CGFloat scale = v.layer.contentsScale; 53 // Use 163 as the standard ppi on iOS. 54 params.dpi = 163*scale; 55 params.sdpi = params.dpi; 56 UIEdgeInsets insets = v.layoutMargins; 57 if (@available(iOS 11.0, tvOS 11.0, *)) { 58 UIFontMetrics *metrics = [UIFontMetrics defaultMetrics]; 59 params.sdpi = [metrics scaledValueForValue:params.sdpi]; 60 insets = v.safeAreaInsets; 61 } 62 params.width = v.bounds.size.width*scale; 63 params.height = v.bounds.size.height*scale; 64 params.top = insets.top*scale; 65 params.right = insets.right*scale; 66 params.bottom = insets.bottom*scale; 67 params.left = insets.left*scale; 68 return params; 69 } 70 */ 71 import "C" 72 73 import ( 74 "image" 75 "runtime" 76 "runtime/debug" 77 "time" 78 "unicode/utf16" 79 "unsafe" 80 81 "github.com/Seikaijyu/gio/f32" 82 "github.com/Seikaijyu/gio/io/clipboard" 83 "github.com/Seikaijyu/gio/io/key" 84 "github.com/Seikaijyu/gio/io/pointer" 85 "github.com/Seikaijyu/gio/io/system" 86 "github.com/Seikaijyu/gio/unit" 87 ) 88 89 type ViewEvent struct { 90 // ViewController is a CFTypeRef for the UIViewController backing a Window. 91 ViewController uintptr 92 } 93 94 type window struct { 95 view C.CFTypeRef 96 w *callbacks 97 displayLink *displayLink 98 99 visible bool 100 cursor pointer.Cursor 101 config Config 102 103 pointerMap []C.CFTypeRef 104 } 105 106 var mainWindow = newWindowRendezvous() 107 108 var views = make(map[C.CFTypeRef]*window) 109 110 func init() { 111 // Darwin requires UI operations happen on the main thread only. 112 runtime.LockOSThread() 113 } 114 115 //export onCreate 116 func onCreate(view, controller C.CFTypeRef) { 117 w := &window{ 118 view: view, 119 } 120 dl, err := newDisplayLink(func() { 121 w.draw(false) 122 }) 123 if err != nil { 124 panic(err) 125 } 126 w.displayLink = dl 127 wopts := <-mainWindow.out 128 w.w = wopts.window 129 w.w.SetDriver(w) 130 views[view] = w 131 w.Configure(wopts.options) 132 w.w.Event(system.StageEvent{Stage: system.StagePaused}) 133 w.w.Event(ViewEvent{ViewController: uintptr(controller)}) 134 } 135 136 //export gio_onDraw 137 func gio_onDraw(view C.CFTypeRef) { 138 w := views[view] 139 w.draw(true) 140 } 141 142 func (w *window) draw(sync bool) { 143 params := C.viewDrawParams(w.view) 144 if params.width == 0 || params.height == 0 { 145 return 146 } 147 wasVisible := w.visible 148 w.visible = true 149 if !wasVisible { 150 w.w.Event(system.StageEvent{Stage: system.StageRunning}) 151 } 152 const inchPrDp = 1.0 / 163 153 m := unit.Metric{ 154 PxPerDp: float32(params.dpi) * inchPrDp, 155 PxPerSp: float32(params.sdpi) * inchPrDp, 156 } 157 dppp := unit.Dp(1. / m.PxPerDp) 158 w.w.Event(frameEvent{ 159 FrameEvent: system.FrameEvent{ 160 Now: time.Now(), 161 Size: image.Point{ 162 X: int(params.width + .5), 163 Y: int(params.height + .5), 164 }, 165 Insets: system.Insets{ 166 Top: unit.Dp(params.top) * dppp, 167 Bottom: unit.Dp(params.bottom) * dppp, 168 Left: unit.Dp(params.left) * dppp, 169 Right: unit.Dp(params.right) * dppp, 170 }, 171 Metric: m, 172 }, 173 Sync: sync, 174 }) 175 } 176 177 //export onStop 178 func onStop(view C.CFTypeRef) { 179 w := views[view] 180 w.visible = false 181 w.w.Event(system.StageEvent{Stage: system.StagePaused}) 182 } 183 184 //export onDestroy 185 func onDestroy(view C.CFTypeRef) { 186 w := views[view] 187 delete(views, view) 188 w.w.Event(ViewEvent{}) 189 w.w.Event(system.DestroyEvent{}) 190 w.displayLink.Close() 191 w.view = 0 192 } 193 194 //export onFocus 195 func onFocus(view C.CFTypeRef, focus int) { 196 w := views[view] 197 w.w.Event(key.FocusEvent{Focus: focus != 0}) 198 } 199 200 //export onLowMemory 201 func onLowMemory() { 202 runtime.GC() 203 debug.FreeOSMemory() 204 } 205 206 //export onUpArrow 207 func onUpArrow(view C.CFTypeRef) { 208 views[view].onKeyCommand(key.NameUpArrow) 209 } 210 211 //export onDownArrow 212 func onDownArrow(view C.CFTypeRef) { 213 views[view].onKeyCommand(key.NameDownArrow) 214 } 215 216 //export onLeftArrow 217 func onLeftArrow(view C.CFTypeRef) { 218 views[view].onKeyCommand(key.NameLeftArrow) 219 } 220 221 //export onRightArrow 222 func onRightArrow(view C.CFTypeRef) { 223 views[view].onKeyCommand(key.NameRightArrow) 224 } 225 226 //export onDeleteBackward 227 func onDeleteBackward(view C.CFTypeRef) { 228 views[view].onKeyCommand(key.NameDeleteBackward) 229 } 230 231 //export onText 232 func onText(view, str C.CFTypeRef) { 233 w := views[view] 234 w.w.EditorInsert(nsstringToString(str)) 235 } 236 237 //export onTouch 238 func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) { 239 var kind pointer.Kind 240 switch phase { 241 case C.UITouchPhaseBegan: 242 kind = pointer.Press 243 case C.UITouchPhaseMoved: 244 kind = pointer.Move 245 case C.UITouchPhaseEnded: 246 kind = pointer.Release 247 case C.UITouchPhaseCancelled: 248 kind = pointer.Cancel 249 default: 250 return 251 } 252 w := views[view] 253 t := time.Duration(float64(ti) * float64(time.Second)) 254 p := f32.Point{X: float32(x), Y: float32(y)} 255 w.w.Event(pointer.Event{ 256 Kind: kind, 257 Source: pointer.Touch, 258 PointerID: w.lookupTouch(last != 0, touchRef), 259 Position: p, 260 Time: t, 261 }) 262 } 263 264 func (w *window) ReadClipboard() { 265 cstr := C.readClipboard() 266 defer C.CFRelease(cstr) 267 content := nsstringToString(cstr) 268 w.w.Event(clipboard.Event{Text: content}) 269 } 270 271 func (w *window) WriteClipboard(s string) { 272 u16 := utf16.Encode([]rune(s)) 273 var chars *C.unichar 274 if len(u16) > 0 { 275 chars = (*C.unichar)(unsafe.Pointer(&u16[0])) 276 } 277 C.writeClipboard(chars, C.NSUInteger(len(u16))) 278 } 279 280 func (w *window) Configure([]Option) { 281 // Decorations are never disabled. 282 w.config.Decorated = true 283 w.w.Event(ConfigEvent{Config: w.config}) 284 } 285 286 func (w *window) EditorStateChanged(old, new editorState) {} 287 288 func (w *window) Perform(system.Action) {} 289 290 func (w *window) SetAnimating(anim bool) { 291 v := w.view 292 if v == 0 { 293 return 294 } 295 if anim { 296 w.displayLink.Start() 297 } else { 298 w.displayLink.Stop() 299 } 300 } 301 302 func (w *window) SetCursor(cursor pointer.Cursor) { 303 w.cursor = windowSetCursor(w.cursor, cursor) 304 } 305 306 func (w *window) onKeyCommand(name string) { 307 w.w.Event(key.Event{ 308 Name: name, 309 }) 310 } 311 312 // lookupTouch maps an UITouch pointer value to an index. If 313 // last is set, the map is cleared. 314 func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID { 315 id := -1 316 for i, ref := range w.pointerMap { 317 if ref == touch { 318 id = i 319 break 320 } 321 } 322 if id == -1 { 323 id = len(w.pointerMap) 324 w.pointerMap = append(w.pointerMap, touch) 325 } 326 if last { 327 w.pointerMap = w.pointerMap[:0] 328 } 329 return pointer.ID(id) 330 } 331 332 func (w *window) contextView() C.CFTypeRef { 333 return w.view 334 } 335 336 func (w *window) ShowTextInput(show bool) { 337 if show { 338 C.showTextInput(w.view) 339 } else { 340 C.hideTextInput(w.view) 341 } 342 } 343 344 func (w *window) SetInputHint(_ key.InputHint) {} 345 346 func newWindow(win *callbacks, options []Option) error { 347 mainWindow.in <- windowAndConfig{win, options} 348 return <-mainWindow.errs 349 } 350 351 func osMain() { 352 } 353 354 //export gio_runMain 355 func gio_runMain() { 356 runMain() 357 } 358 359 func (_ ViewEvent) ImplementsEvent() {}