os_ios.go 9.3 KB

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