driver.go 14 KB

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