driver.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. package mobile
  2. import (
  3. "runtime"
  4. "strconv"
  5. "time"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/canvas"
  8. "fyne.io/fyne/v2/internal"
  9. "fyne.io/fyne/v2/internal/animation"
  10. intapp "fyne.io/fyne/v2/internal/app"
  11. "fyne.io/fyne/v2/internal/cache"
  12. "fyne.io/fyne/v2/internal/driver"
  13. "fyne.io/fyne/v2/internal/driver/common"
  14. "fyne.io/fyne/v2/internal/driver/mobile/app"
  15. "fyne.io/fyne/v2/internal/driver/mobile/event/key"
  16. "fyne.io/fyne/v2/internal/driver/mobile/event/lifecycle"
  17. "fyne.io/fyne/v2/internal/driver/mobile/event/paint"
  18. "fyne.io/fyne/v2/internal/driver/mobile/event/size"
  19. "fyne.io/fyne/v2/internal/driver/mobile/event/touch"
  20. "fyne.io/fyne/v2/internal/driver/mobile/gl"
  21. "fyne.io/fyne/v2/internal/painter"
  22. pgl "fyne.io/fyne/v2/internal/painter/gl"
  23. "fyne.io/fyne/v2/theme"
  24. )
  25. const (
  26. tapMoveThreshold = 4.0 // how far can we move before it is a drag
  27. tapSecondaryDelay = 300 * time.Millisecond // how long before secondary tap
  28. )
  29. // Configuration is the system information about the current device
  30. type Configuration struct {
  31. SystemTheme fyne.ThemeVariant
  32. }
  33. // ConfiguredDriver is a simple type that allows packages to hook into configuration changes of this driver.
  34. type ConfiguredDriver interface {
  35. SetOnConfigurationChanged(func(*Configuration))
  36. }
  37. type mobileDriver struct {
  38. app app.App
  39. glctx gl.Context
  40. windows []fyne.Window
  41. device *device
  42. animation *animation.Runner
  43. currentSize size.Event
  44. theme fyne.ThemeVariant
  45. onConfigChanged func(*Configuration)
  46. painting bool
  47. }
  48. // Declare conformity with Driver
  49. var _ fyne.Driver = (*mobileDriver)(nil)
  50. var _ ConfiguredDriver = (*mobileDriver)(nil)
  51. func init() {
  52. runtime.LockOSThread()
  53. }
  54. func (d *mobileDriver) CreateWindow(title string) fyne.Window {
  55. c := NewCanvas().(*mobileCanvas) // silence lint
  56. ret := &window{title: title, canvas: c, isChild: len(d.windows) > 0}
  57. ret.InitEventQueue()
  58. go ret.RunEventQueue()
  59. c.setContent(&canvas.Rectangle{FillColor: theme.BackgroundColor()})
  60. c.SetPainter(pgl.NewPainter(c, ret))
  61. d.windows = append(d.windows, ret)
  62. return ret
  63. }
  64. func (d *mobileDriver) AllWindows() []fyne.Window {
  65. return d.windows
  66. }
  67. // currentWindow returns the most recently opened window - we can only show one at a time.
  68. func (d *mobileDriver) currentWindow() *window {
  69. if len(d.windows) == 0 {
  70. return nil
  71. }
  72. var last *window
  73. for i := len(d.windows) - 1; i >= 0; i-- {
  74. last = d.windows[i].(*window)
  75. if last.visible {
  76. return last
  77. }
  78. }
  79. return last
  80. }
  81. func (d *mobileDriver) RenderedTextSize(text string, textSize float32, style fyne.TextStyle) (size fyne.Size, baseline float32) {
  82. return painter.RenderedTextSize(text, textSize, style)
  83. }
  84. func (d *mobileDriver) CanvasForObject(obj fyne.CanvasObject) fyne.Canvas {
  85. if len(d.windows) == 0 {
  86. return nil
  87. }
  88. // TODO figure out how we handle multiple windows...
  89. return d.currentWindow().Canvas()
  90. }
  91. func (d *mobileDriver) AbsolutePositionForObject(co fyne.CanvasObject) fyne.Position {
  92. c := d.CanvasForObject(co)
  93. if c == nil {
  94. return fyne.NewPos(0, 0)
  95. }
  96. mc := c.(*mobileCanvas)
  97. pos := driver.AbsolutePositionForObject(co, mc.ObjectTrees())
  98. inset, _ := c.InteractiveArea()
  99. if mc.windowHead != nil {
  100. if len(mc.windowHead.(*fyne.Container).Objects) > 1 {
  101. topHeight := mc.windowHead.MinSize().Height
  102. pos = pos.Subtract(fyne.NewSize(0, topHeight))
  103. }
  104. }
  105. return pos.Subtract(inset)
  106. }
  107. func (d *mobileDriver) Quit() {
  108. // Android and iOS guidelines say this should not be allowed!
  109. }
  110. func (d *mobileDriver) Run() {
  111. app.Main(func(a app.App) {
  112. d.app = a
  113. settingsChange := make(chan fyne.Settings)
  114. fyne.CurrentApp().Settings().AddChangeListener(settingsChange)
  115. draw := time.NewTicker(time.Second / 60)
  116. for {
  117. select {
  118. case <-draw.C:
  119. d.sendPaintEvent()
  120. case set := <-settingsChange:
  121. painter.ClearFontCache()
  122. cache.ResetThemeCaches()
  123. intapp.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) {
  124. c, ok := w.Canvas().(*mobileCanvas)
  125. if !ok {
  126. return
  127. }
  128. c.applyThemeOutOfTreeObjects()
  129. })
  130. case e, ok := <-a.Events():
  131. if !ok {
  132. return // events channel closed, app done
  133. }
  134. current := d.currentWindow()
  135. if current == nil {
  136. continue
  137. }
  138. c := current.Canvas().(*mobileCanvas)
  139. switch e := a.Filter(e).(type) {
  140. case lifecycle.Event:
  141. d.handleLifecycle(e, current)
  142. case size.Event:
  143. if e.WidthPx <= 0 {
  144. continue
  145. }
  146. d.currentSize = e
  147. currentOrientation = e.Orientation
  148. currentDPI = e.PixelsPerPt * 72
  149. d.setTheme(e.DarkMode)
  150. dev := d.device
  151. dev.safeTop = e.InsetTopPx
  152. dev.safeLeft = e.InsetLeftPx
  153. dev.safeHeight = e.HeightPx - e.InsetTopPx - e.InsetBottomPx
  154. dev.safeWidth = e.WidthPx - e.InsetLeftPx - e.InsetRightPx
  155. c.scale = fyne.CurrentDevice().SystemScaleForWindow(nil)
  156. c.Painter().SetFrameBufferScale(1.0)
  157. // make sure that we paint on the next frame
  158. c.Content().Refresh()
  159. case paint.Event:
  160. d.handlePaint(e, current)
  161. case touch.Event:
  162. switch e.Type {
  163. case touch.TypeBegin:
  164. d.tapDownCanvas(current, e.X, e.Y, e.Sequence)
  165. case touch.TypeMove:
  166. d.tapMoveCanvas(current, e.X, e.Y, e.Sequence)
  167. case touch.TypeEnd:
  168. d.tapUpCanvas(current, e.X, e.Y, e.Sequence)
  169. }
  170. case key.Event:
  171. if e.Direction == key.DirPress {
  172. d.typeDownCanvas(c, e.Rune, e.Code, e.Modifiers)
  173. } else if e.Direction == key.DirRelease {
  174. d.typeUpCanvas(c, e.Rune, e.Code, e.Modifiers)
  175. }
  176. }
  177. }
  178. }
  179. })
  180. }
  181. func (d *mobileDriver) handleLifecycle(e lifecycle.Event, w fyne.Window) {
  182. c := w.Canvas().(*mobileCanvas)
  183. switch e.Crosses(lifecycle.StageVisible) {
  184. case lifecycle.CrossOn:
  185. d.glctx, _ = e.DrawContext.(gl.Context)
  186. d.onStart()
  187. // this is a fix for some android phone to prevent the app from being drawn as a blank screen after being pushed in the background
  188. c.Content().Refresh()
  189. d.sendPaintEvent()
  190. case lifecycle.CrossOff:
  191. d.onStop()
  192. d.glctx = nil
  193. }
  194. switch e.Crosses(lifecycle.StageFocused) {
  195. case lifecycle.CrossOn: // foregrounding
  196. fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerEnteredForeground()
  197. case lifecycle.CrossOff: // will enter background
  198. if runtime.GOOS == "darwin" {
  199. if d.glctx == nil {
  200. return
  201. }
  202. s := fyne.NewSize(float32(d.currentSize.WidthPx)/c.scale, float32(d.currentSize.HeightPx)/c.scale)
  203. d.paintWindow(w, s)
  204. d.app.Publish()
  205. }
  206. fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerExitedForeground()
  207. }
  208. }
  209. func (d *mobileDriver) handlePaint(e paint.Event, w fyne.Window) {
  210. c := w.Canvas().(*mobileCanvas)
  211. d.painting = false
  212. if d.glctx == nil || e.External {
  213. return
  214. }
  215. if !c.inited {
  216. c.inited = true
  217. c.Painter().Init() // we cannot init until the context is set above
  218. }
  219. canvasNeedRefresh := c.FreeDirtyTextures() > 0 || c.CheckDirtyAndClear()
  220. if canvasNeedRefresh {
  221. newSize := fyne.NewSize(float32(d.currentSize.WidthPx)/c.scale, float32(d.currentSize.HeightPx)/c.scale)
  222. if c.EnsureMinSize() {
  223. c.sizeContent(newSize) // force resize of content
  224. } else { // if screen changed
  225. w.Resize(newSize)
  226. }
  227. d.paintWindow(w, newSize)
  228. d.app.Publish()
  229. }
  230. cache.Clean(canvasNeedRefresh)
  231. }
  232. func (d *mobileDriver) onStart() {
  233. fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerStarted()
  234. }
  235. func (d *mobileDriver) onStop() {
  236. fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerStopped()
  237. }
  238. func (d *mobileDriver) paintWindow(window fyne.Window, size fyne.Size) {
  239. clips := &internal.ClipStack{}
  240. c := window.Canvas().(*mobileCanvas)
  241. r, g, b, a := theme.BackgroundColor().RGBA()
  242. max16bit := float32(255 * 255)
  243. d.glctx.ClearColor(float32(r)/max16bit, float32(g)/max16bit, float32(b)/max16bit, float32(a)/max16bit)
  244. d.glctx.Clear(gl.ColorBufferBit)
  245. draw := func(node *common.RenderCacheNode, pos fyne.Position) {
  246. obj := node.Obj()
  247. if _, ok := obj.(fyne.Scrollable); ok {
  248. inner := clips.Push(pos, obj.Size())
  249. c.Painter().StartClipping(inner.Rect())
  250. }
  251. if size.Width <= 0 || size.Height <= 0 { // iconifying on Windows can do bad things
  252. return
  253. }
  254. c.Painter().Paint(obj, pos, size)
  255. }
  256. afterDraw := func(node *common.RenderCacheNode) {
  257. if _, ok := node.Obj().(fyne.Scrollable); ok {
  258. c.Painter().StopClipping()
  259. clips.Pop()
  260. if top := clips.Top(); top != nil {
  261. c.Painter().StartClipping(top.Rect())
  262. }
  263. }
  264. }
  265. c.WalkTrees(draw, afterDraw)
  266. }
  267. func (d *mobileDriver) sendPaintEvent() {
  268. if d.painting {
  269. return
  270. }
  271. d.app.Send(paint.Event{})
  272. d.painting = true
  273. }
  274. func (d *mobileDriver) setTheme(dark bool) {
  275. var mode fyne.ThemeVariant
  276. if dark {
  277. mode = theme.VariantDark
  278. } else {
  279. mode = theme.VariantLight
  280. }
  281. if d.theme != mode && d.onConfigChanged != nil {
  282. d.onConfigChanged(&Configuration{SystemTheme: mode})
  283. }
  284. d.theme = mode
  285. }
  286. func (d *mobileDriver) tapDownCanvas(w *window, x, y float32, tapID touch.Sequence) {
  287. tapX := internal.UnscaleInt(w.canvas, int(x))
  288. tapY := internal.UnscaleInt(w.canvas, int(y))
  289. pos := fyne.NewPos(tapX, tapY+tapYOffset)
  290. w.canvas.tapDown(pos, int(tapID))
  291. }
  292. func (d *mobileDriver) tapMoveCanvas(w *window, x, y float32, tapID touch.Sequence) {
  293. tapX := internal.UnscaleInt(w.canvas, int(x))
  294. tapY := internal.UnscaleInt(w.canvas, int(y))
  295. pos := fyne.NewPos(tapX, tapY+tapYOffset)
  296. w.canvas.tapMove(pos, int(tapID), func(wid fyne.Draggable, ev *fyne.DragEvent) {
  297. w.QueueEvent(func() { wid.Dragged(ev) })
  298. })
  299. }
  300. func (d *mobileDriver) tapUpCanvas(w *window, x, y float32, tapID touch.Sequence) {
  301. tapX := internal.UnscaleInt(w.canvas, int(x))
  302. tapY := internal.UnscaleInt(w.canvas, int(y))
  303. pos := fyne.NewPos(tapX, tapY+tapYOffset)
  304. w.canvas.tapUp(pos, int(tapID), func(wid fyne.Tappable, ev *fyne.PointEvent) {
  305. w.QueueEvent(func() { wid.Tapped(ev) })
  306. }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) {
  307. w.QueueEvent(func() { wid.TappedSecondary(ev) })
  308. }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) {
  309. w.QueueEvent(func() { wid.DoubleTapped(ev) })
  310. }, func(wid fyne.Draggable) {
  311. w.QueueEvent(wid.DragEnd)
  312. })
  313. }
  314. var keyCodeMap = map[key.Code]fyne.KeyName{
  315. // non-printable
  316. key.CodeEscape: fyne.KeyEscape,
  317. key.CodeReturnEnter: fyne.KeyReturn,
  318. key.CodeTab: fyne.KeyTab,
  319. key.CodeDeleteBackspace: fyne.KeyBackspace,
  320. key.CodeInsert: fyne.KeyInsert,
  321. key.CodePageUp: fyne.KeyPageUp,
  322. key.CodePageDown: fyne.KeyPageDown,
  323. key.CodeHome: fyne.KeyHome,
  324. key.CodeEnd: fyne.KeyEnd,
  325. key.CodeF1: fyne.KeyF1,
  326. key.CodeF2: fyne.KeyF2,
  327. key.CodeF3: fyne.KeyF3,
  328. key.CodeF4: fyne.KeyF4,
  329. key.CodeF5: fyne.KeyF5,
  330. key.CodeF6: fyne.KeyF6,
  331. key.CodeF7: fyne.KeyF7,
  332. key.CodeF8: fyne.KeyF8,
  333. key.CodeF9: fyne.KeyF9,
  334. key.CodeF10: fyne.KeyF10,
  335. key.CodeF11: fyne.KeyF11,
  336. key.CodeF12: fyne.KeyF12,
  337. key.CodeKeypadEnter: fyne.KeyEnter,
  338. // printable
  339. key.CodeA: fyne.KeyA,
  340. key.CodeB: fyne.KeyB,
  341. key.CodeC: fyne.KeyC,
  342. key.CodeD: fyne.KeyD,
  343. key.CodeE: fyne.KeyE,
  344. key.CodeF: fyne.KeyF,
  345. key.CodeG: fyne.KeyG,
  346. key.CodeH: fyne.KeyH,
  347. key.CodeI: fyne.KeyI,
  348. key.CodeJ: fyne.KeyJ,
  349. key.CodeK: fyne.KeyK,
  350. key.CodeL: fyne.KeyL,
  351. key.CodeM: fyne.KeyM,
  352. key.CodeN: fyne.KeyN,
  353. key.CodeO: fyne.KeyO,
  354. key.CodeP: fyne.KeyP,
  355. key.CodeQ: fyne.KeyQ,
  356. key.CodeR: fyne.KeyR,
  357. key.CodeS: fyne.KeyS,
  358. key.CodeT: fyne.KeyT,
  359. key.CodeU: fyne.KeyU,
  360. key.CodeV: fyne.KeyV,
  361. key.CodeW: fyne.KeyW,
  362. key.CodeX: fyne.KeyX,
  363. key.CodeY: fyne.KeyY,
  364. key.CodeZ: fyne.KeyZ,
  365. key.Code0: fyne.Key0,
  366. key.CodeKeypad0: fyne.Key0,
  367. key.Code1: fyne.Key1,
  368. key.CodeKeypad1: fyne.Key1,
  369. key.Code2: fyne.Key2,
  370. key.CodeKeypad2: fyne.Key2,
  371. key.Code3: fyne.Key3,
  372. key.CodeKeypad3: fyne.Key3,
  373. key.Code4: fyne.Key4,
  374. key.CodeKeypad4: fyne.Key4,
  375. key.Code5: fyne.Key5,
  376. key.CodeKeypad5: fyne.Key5,
  377. key.Code6: fyne.Key6,
  378. key.CodeKeypad6: fyne.Key6,
  379. key.Code7: fyne.Key7,
  380. key.CodeKeypad7: fyne.Key7,
  381. key.Code8: fyne.Key8,
  382. key.CodeKeypad8: fyne.Key8,
  383. key.Code9: fyne.Key9,
  384. key.CodeKeypad9: fyne.Key9,
  385. key.CodeSemicolon: fyne.KeySemicolon,
  386. key.CodeEqualSign: fyne.KeyEqual,
  387. key.CodeSpacebar: fyne.KeySpace,
  388. key.CodeApostrophe: fyne.KeyApostrophe,
  389. key.CodeComma: fyne.KeyComma,
  390. key.CodeHyphenMinus: fyne.KeyMinus,
  391. key.CodeKeypadHyphenMinus: fyne.KeyMinus,
  392. key.CodeFullStop: fyne.KeyPeriod,
  393. key.CodeKeypadFullStop: fyne.KeyPeriod,
  394. key.CodeSlash: fyne.KeySlash,
  395. key.CodeLeftSquareBracket: fyne.KeyLeftBracket,
  396. key.CodeBackslash: fyne.KeyBackslash,
  397. key.CodeRightSquareBracket: fyne.KeyRightBracket,
  398. key.CodeGraveAccent: fyne.KeyBackTick,
  399. }
  400. func keyToName(code key.Code) fyne.KeyName {
  401. ret, ok := keyCodeMap[code]
  402. if !ok {
  403. return ""
  404. }
  405. return ret
  406. }
  407. func runeToPrintable(r rune) rune {
  408. if strconv.IsPrint(r) {
  409. return r
  410. }
  411. return 0
  412. }
  413. func (d *mobileDriver) typeDownCanvas(canvas *mobileCanvas, r rune, code key.Code, mod key.Modifiers) {
  414. keyName := keyToName(code)
  415. switch keyName {
  416. case fyne.KeyTab:
  417. capture := false
  418. if ent, ok := canvas.Focused().(fyne.Tabbable); ok {
  419. capture = ent.AcceptsTab()
  420. }
  421. if !capture {
  422. switch mod {
  423. case 0:
  424. canvas.FocusNext()
  425. return
  426. case key.ModShift:
  427. canvas.FocusPrevious()
  428. return
  429. }
  430. }
  431. }
  432. r = runeToPrintable(r)
  433. keyEvent := &fyne.KeyEvent{Name: keyName}
  434. if canvas.Focused() != nil {
  435. if keyName != "" {
  436. canvas.Focused().TypedKey(keyEvent)
  437. }
  438. if r > 0 {
  439. canvas.Focused().TypedRune(r)
  440. }
  441. } else {
  442. if keyName != "" && canvas.onTypedKey != nil {
  443. canvas.onTypedKey(keyEvent)
  444. }
  445. if r > 0 && canvas.onTypedRune != nil {
  446. canvas.onTypedRune(r)
  447. }
  448. }
  449. }
  450. func (d *mobileDriver) typeUpCanvas(_ *mobileCanvas, _ rune, _ key.Code, _ key.Modifiers) {
  451. }
  452. func (d *mobileDriver) Device() fyne.Device {
  453. if d.device == nil {
  454. d.device = &device{}
  455. }
  456. return d.device
  457. }
  458. func (d *mobileDriver) SetOnConfigurationChanged(f func(*Configuration)) {
  459. d.onConfigChanged = f
  460. }
  461. // NewGoMobileDriver sets up a new Driver instance implemented using the Go
  462. // Mobile extension and OpenGL bindings.
  463. func NewGoMobileDriver() fyne.Driver {
  464. d := new(mobileDriver)
  465. d.theme = fyne.ThemeVariant(2) // unspecified
  466. d.animation = &animation.Runner{}
  467. registerRepository(d)
  468. return d
  469. }