canvas.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. package common
  2. import (
  3. "image/color"
  4. "reflect"
  5. "sync"
  6. "sync/atomic"
  7. "fyne.io/fyne/v2"
  8. "fyne.io/fyne/v2/canvas"
  9. "fyne.io/fyne/v2/internal"
  10. "fyne.io/fyne/v2/internal/app"
  11. "fyne.io/fyne/v2/internal/async"
  12. "fyne.io/fyne/v2/internal/cache"
  13. "fyne.io/fyne/v2/internal/driver"
  14. "fyne.io/fyne/v2/internal/painter/gl"
  15. )
  16. // SizeableCanvas defines a canvas with size related functions.
  17. type SizeableCanvas interface {
  18. fyne.Canvas
  19. Resize(fyne.Size)
  20. MinSize() fyne.Size
  21. }
  22. // Canvas defines common canvas implementation.
  23. type Canvas struct {
  24. sync.RWMutex
  25. OnFocus func(obj fyne.Focusable)
  26. OnUnfocus func()
  27. impl SizeableCanvas
  28. contentFocusMgr *app.FocusManager
  29. menuFocusMgr *app.FocusManager
  30. overlays *overlayStack
  31. shortcut fyne.ShortcutHandler
  32. painter gl.Painter
  33. // Any object that requestes to enter to the refresh queue should
  34. // not be omitted as it is always a rendering task's decision
  35. // for skipping frames or drawing calls.
  36. //
  37. // If an object failed to ender the refresh queue, the object may
  38. // disappear or blink from the view at any frames. As of this reason,
  39. // the refreshQueue is an unbounded queue which is bale to cache
  40. // arbitrary number of fyne.CanvasObject for the rendering.
  41. refreshQueue *async.CanvasObjectQueue
  42. dirty uint32 // atomic
  43. mWindowHeadTree, contentTree, menuTree *renderCacheTree
  44. }
  45. // AddShortcut adds a shortcut to the canvas.
  46. func (c *Canvas) AddShortcut(shortcut fyne.Shortcut, handler func(shortcut fyne.Shortcut)) {
  47. c.shortcut.AddShortcut(shortcut, handler)
  48. }
  49. func (c *Canvas) DrawDebugOverlay(obj fyne.CanvasObject, pos fyne.Position, size fyne.Size) {
  50. switch obj.(type) {
  51. case fyne.Widget:
  52. r := canvas.NewRectangle(color.Transparent)
  53. r.StrokeColor = color.NRGBA{R: 0xcc, G: 0x33, B: 0x33, A: 0xff}
  54. r.StrokeWidth = 1
  55. r.Resize(obj.Size())
  56. c.Painter().Paint(r, pos, size)
  57. t := canvas.NewText(reflect.ValueOf(obj).Elem().Type().Name(), r.StrokeColor)
  58. t.TextSize = 10
  59. c.Painter().Paint(t, pos.AddXY(2, 2), size)
  60. case *fyne.Container:
  61. r := canvas.NewRectangle(color.Transparent)
  62. r.StrokeColor = color.NRGBA{R: 0x33, G: 0x33, B: 0xcc, A: 0xff}
  63. r.StrokeWidth = 1
  64. r.Resize(obj.Size())
  65. c.Painter().Paint(r, pos, size)
  66. }
  67. }
  68. // EnsureMinSize ensure canvas min size.
  69. //
  70. // This function uses lock.
  71. func (c *Canvas) EnsureMinSize() bool {
  72. if c.impl.Content() == nil {
  73. return false
  74. }
  75. windowNeedsMinSizeUpdate := false
  76. csize := c.impl.Size()
  77. min := c.impl.MinSize()
  78. c.RLock()
  79. defer c.RUnlock()
  80. var parentNeedingUpdate *RenderCacheNode
  81. ensureMinSize := func(node *RenderCacheNode, pos fyne.Position) {
  82. obj := node.obj
  83. cache.SetCanvasForObject(obj, c.impl, func() {
  84. if img, ok := obj.(*canvas.Image); ok {
  85. c.RUnlock()
  86. img.Refresh() // this may now have a different texScale
  87. c.RLock()
  88. }
  89. })
  90. if parentNeedingUpdate == node {
  91. c.updateLayout(obj)
  92. parentNeedingUpdate = nil
  93. }
  94. c.RUnlock()
  95. if !obj.Visible() {
  96. c.RLock()
  97. return
  98. }
  99. minSize := obj.MinSize()
  100. c.RLock()
  101. minSizeChanged := node.minSize != minSize
  102. if minSizeChanged {
  103. node.minSize = minSize
  104. if node.parent != nil {
  105. parentNeedingUpdate = node.parent
  106. } else {
  107. windowNeedsMinSizeUpdate = true
  108. c.RUnlock()
  109. size := obj.Size()
  110. c.RLock()
  111. expectedSize := minSize.Max(size)
  112. if expectedSize != size && size != csize {
  113. c.RUnlock()
  114. obj.Resize(expectedSize)
  115. c.RLock()
  116. } else {
  117. c.updateLayout(obj)
  118. }
  119. }
  120. }
  121. }
  122. c.WalkTrees(nil, ensureMinSize)
  123. shouldResize := windowNeedsMinSizeUpdate && (csize.Width < min.Width || csize.Height < min.Height)
  124. if shouldResize {
  125. c.RUnlock()
  126. c.impl.Resize(csize.Max(min))
  127. c.RLock()
  128. }
  129. return windowNeedsMinSizeUpdate
  130. }
  131. // Focus makes the provided item focused.
  132. func (c *Canvas) Focus(obj fyne.Focusable) {
  133. focusMgr := c.focusManager()
  134. if focusMgr != nil && focusMgr.Focus(obj) { // fast path – probably >99.9% of all cases
  135. if c.OnFocus != nil {
  136. c.OnFocus(obj)
  137. }
  138. return
  139. }
  140. c.RLock()
  141. focusMgrs := append([]*app.FocusManager{c.contentFocusMgr, c.menuFocusMgr}, c.overlays.ListFocusManagers()...)
  142. c.RUnlock()
  143. for _, mgr := range focusMgrs {
  144. if mgr == nil {
  145. continue
  146. }
  147. if focusMgr != mgr {
  148. if mgr.Focus(obj) {
  149. if c.OnFocus != nil {
  150. c.OnFocus(obj)
  151. }
  152. return
  153. }
  154. }
  155. }
  156. fyne.LogError("Failed to focus object which is not part of the canvas’ content, menu or overlays.", nil)
  157. }
  158. // Focused returns the current focused object.
  159. func (c *Canvas) Focused() fyne.Focusable {
  160. mgr := c.focusManager()
  161. if mgr == nil {
  162. return nil
  163. }
  164. return mgr.Focused()
  165. }
  166. // FocusGained signals to the manager that its content got focus.
  167. // Valid only on Desktop.
  168. func (c *Canvas) FocusGained() {
  169. mgr := c.focusManager()
  170. if mgr == nil {
  171. return
  172. }
  173. mgr.FocusGained()
  174. }
  175. // FocusLost signals to the manager that its content lost focus.
  176. // Valid only on Desktop.
  177. func (c *Canvas) FocusLost() {
  178. mgr := c.focusManager()
  179. if mgr == nil {
  180. return
  181. }
  182. mgr.FocusLost()
  183. }
  184. // FocusNext focuses the next focusable item.
  185. func (c *Canvas) FocusNext() {
  186. mgr := c.focusManager()
  187. if mgr == nil {
  188. return
  189. }
  190. mgr.FocusNext()
  191. }
  192. // FocusPrevious focuses the previous focusable item.
  193. func (c *Canvas) FocusPrevious() {
  194. mgr := c.focusManager()
  195. if mgr == nil {
  196. return
  197. }
  198. mgr.FocusPrevious()
  199. }
  200. // FreeDirtyTextures frees dirty textures and returns the number of freed textures.
  201. func (c *Canvas) FreeDirtyTextures() (freed uint64) {
  202. freeObject := func(object fyne.CanvasObject) {
  203. freeWalked := func(obj fyne.CanvasObject, _ fyne.Position, _ fyne.Position, _ fyne.Size) bool {
  204. // No image refresh while recursing to avoid double texture upload.
  205. if _, ok := obj.(*canvas.Image); ok {
  206. return false
  207. }
  208. if c.painter != nil {
  209. c.painter.Free(obj)
  210. }
  211. return false
  212. }
  213. // Image.Refresh will trigger a refresh specific to the object, while recursing on parent widget would just lead to
  214. // a double texture upload.
  215. if img, ok := object.(*canvas.Image); ok {
  216. if c.painter != nil {
  217. c.painter.Free(img)
  218. }
  219. } else {
  220. driver.WalkCompleteObjectTree(object, freeWalked, nil)
  221. }
  222. }
  223. // Within a frame, refresh tasks are requested from the Refresh method,
  224. // and we desire to clear out all requested operations within a frame.
  225. // See https://github.com/fyne-io/fyne/issues/2548.
  226. tasksToDo := c.refreshQueue.Len()
  227. shouldFilterDuplicates := (tasksToDo > 200) // filtering has overhead, not worth enabling for few tasks
  228. var refreshSet map[fyne.CanvasObject]struct{}
  229. if shouldFilterDuplicates {
  230. refreshSet = make(map[fyne.CanvasObject]struct{})
  231. }
  232. for c.refreshQueue.Len() > 0 {
  233. object := c.refreshQueue.Out()
  234. if !shouldFilterDuplicates {
  235. freed++
  236. freeObject(object)
  237. } else {
  238. refreshSet[object] = struct{}{}
  239. tasksToDo--
  240. if tasksToDo == 0 {
  241. shouldFilterDuplicates = false // stop collecting messages to avoid starvation
  242. for object := range refreshSet {
  243. freed++
  244. freeObject(object)
  245. }
  246. }
  247. }
  248. }
  249. cache.RangeExpiredTexturesFor(c.impl, func(obj fyne.CanvasObject) {
  250. if c.painter != nil {
  251. c.painter.Free(obj)
  252. }
  253. })
  254. return
  255. }
  256. // Initialize initializes the canvas.
  257. func (c *Canvas) Initialize(impl SizeableCanvas, onOverlayChanged func()) {
  258. c.impl = impl
  259. c.refreshQueue = async.NewCanvasObjectQueue()
  260. c.overlays = &overlayStack{
  261. OverlayStack: internal.OverlayStack{
  262. OnChange: onOverlayChanged,
  263. Canvas: impl,
  264. },
  265. }
  266. }
  267. // ObjectTrees return canvas object trees.
  268. //
  269. // This function uses lock.
  270. func (c *Canvas) ObjectTrees() []fyne.CanvasObject {
  271. c.RLock()
  272. var content, menu fyne.CanvasObject
  273. if c.contentTree != nil && c.contentTree.root != nil {
  274. content = c.contentTree.root.obj
  275. }
  276. if c.menuTree != nil && c.menuTree.root != nil {
  277. menu = c.menuTree.root.obj
  278. }
  279. c.RUnlock()
  280. trees := make([]fyne.CanvasObject, 0, len(c.Overlays().List())+2)
  281. trees = append(trees, content)
  282. if menu != nil {
  283. trees = append(trees, menu)
  284. }
  285. trees = append(trees, c.Overlays().List()...)
  286. return trees
  287. }
  288. // Overlays returns the overlay stack.
  289. func (c *Canvas) Overlays() fyne.OverlayStack {
  290. // we don't need to lock here, because overlays never changes
  291. return c.overlays
  292. }
  293. // Painter returns the canvas painter.
  294. func (c *Canvas) Painter() gl.Painter {
  295. return c.painter
  296. }
  297. // Refresh refreshes a canvas object.
  298. func (c *Canvas) Refresh(obj fyne.CanvasObject) {
  299. walkNeeded := false
  300. switch obj.(type) {
  301. case *fyne.Container:
  302. walkNeeded = true
  303. case fyne.Widget:
  304. walkNeeded = true
  305. }
  306. if walkNeeded {
  307. driver.WalkCompleteObjectTree(obj, func(co fyne.CanvasObject, p1, p2 fyne.Position, s fyne.Size) bool {
  308. if i, ok := co.(*canvas.Image); ok {
  309. i.Refresh()
  310. }
  311. return false
  312. }, nil)
  313. }
  314. c.refreshQueue.In(obj)
  315. c.SetDirty()
  316. }
  317. // RemoveShortcut removes a shortcut from the canvas.
  318. func (c *Canvas) RemoveShortcut(shortcut fyne.Shortcut) {
  319. c.shortcut.RemoveShortcut(shortcut)
  320. }
  321. // SetContentTreeAndFocusMgr sets content tree and focus manager.
  322. //
  323. // This function does not use the canvas lock.
  324. func (c *Canvas) SetContentTreeAndFocusMgr(content fyne.CanvasObject) {
  325. c.contentTree = &renderCacheTree{root: &RenderCacheNode{obj: content}}
  326. var focused fyne.Focusable
  327. if c.contentFocusMgr != nil {
  328. focused = c.contentFocusMgr.Focused() // keep old focus if possible
  329. }
  330. c.contentFocusMgr = app.NewFocusManager(content)
  331. if focused != nil {
  332. c.contentFocusMgr.Focus(focused)
  333. }
  334. }
  335. // CheckDirtyAndClear returns true if the canvas is dirty and
  336. // clears the dirty state atomically.
  337. func (c *Canvas) CheckDirtyAndClear() bool {
  338. return atomic.SwapUint32(&c.dirty, 0) != 0
  339. }
  340. // SetDirty sets canvas dirty flag atomically.
  341. func (c *Canvas) SetDirty() {
  342. atomic.AddUint32(&c.dirty, 1)
  343. }
  344. // SetMenuTreeAndFocusMgr sets menu tree and focus manager.
  345. //
  346. // This function does not use the canvas lock.
  347. func (c *Canvas) SetMenuTreeAndFocusMgr(menu fyne.CanvasObject) {
  348. c.menuTree = &renderCacheTree{root: &RenderCacheNode{obj: menu}}
  349. if menu != nil {
  350. c.menuFocusMgr = app.NewFocusManager(menu)
  351. } else {
  352. c.menuFocusMgr = nil
  353. }
  354. }
  355. // SetMobileWindowHeadTree sets window head tree.
  356. //
  357. // This function does not use the canvas lock.
  358. func (c *Canvas) SetMobileWindowHeadTree(head fyne.CanvasObject) {
  359. c.mWindowHeadTree = &renderCacheTree{root: &RenderCacheNode{obj: head}}
  360. }
  361. // SetPainter sets the canvas painter.
  362. func (c *Canvas) SetPainter(p gl.Painter) {
  363. c.painter = p
  364. }
  365. // TypedShortcut handle the registered shortcut.
  366. func (c *Canvas) TypedShortcut(shortcut fyne.Shortcut) {
  367. c.shortcut.TypedShortcut(shortcut)
  368. }
  369. // Unfocus unfocuses all the objects in the canvas.
  370. func (c *Canvas) Unfocus() {
  371. mgr := c.focusManager()
  372. if mgr == nil {
  373. return
  374. }
  375. if mgr.Focus(nil) && c.OnUnfocus != nil {
  376. c.OnUnfocus()
  377. }
  378. }
  379. // WalkTrees walks over the trees.
  380. func (c *Canvas) WalkTrees(
  381. beforeChildren func(*RenderCacheNode, fyne.Position),
  382. afterChildren func(*RenderCacheNode, fyne.Position),
  383. ) {
  384. c.walkTree(c.contentTree, beforeChildren, afterChildren)
  385. if c.mWindowHeadTree != nil && c.mWindowHeadTree.root.obj != nil {
  386. c.walkTree(c.mWindowHeadTree, beforeChildren, afterChildren)
  387. }
  388. if c.menuTree != nil && c.menuTree.root.obj != nil {
  389. c.walkTree(c.menuTree, beforeChildren, afterChildren)
  390. }
  391. for _, tree := range c.overlays.renderCaches {
  392. if tree != nil {
  393. c.walkTree(tree, beforeChildren, afterChildren)
  394. }
  395. }
  396. }
  397. func (c *Canvas) focusManager() *app.FocusManager {
  398. if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
  399. return focusMgr
  400. }
  401. c.RLock()
  402. defer c.RUnlock()
  403. if c.isMenuActive() {
  404. return c.menuFocusMgr
  405. }
  406. return c.contentFocusMgr
  407. }
  408. func (c *Canvas) isMenuActive() bool {
  409. if c.menuTree == nil || c.menuTree.root == nil || c.menuTree.root.obj == nil {
  410. return false
  411. }
  412. menu := c.menuTree.root.obj
  413. if am, ok := menu.(activatableMenu); ok {
  414. return am.IsActive()
  415. }
  416. return true
  417. }
  418. func (c *Canvas) walkTree(
  419. tree *renderCacheTree,
  420. beforeChildren func(*RenderCacheNode, fyne.Position),
  421. afterChildren func(*RenderCacheNode, fyne.Position),
  422. ) {
  423. tree.Lock()
  424. defer tree.Unlock()
  425. var node, parent, prev *RenderCacheNode
  426. node = tree.root
  427. bc := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.Position, _ fyne.Size) bool {
  428. if node != nil && node.obj != obj {
  429. if parent.firstChild == node {
  430. parent.firstChild = nil
  431. }
  432. node = nil
  433. }
  434. if node == nil {
  435. node = &RenderCacheNode{parent: parent, obj: obj}
  436. if parent.firstChild == nil {
  437. parent.firstChild = node
  438. } else {
  439. prev.nextSibling = node
  440. }
  441. }
  442. if prev != nil && prev.parent != parent {
  443. prev = nil
  444. }
  445. if beforeChildren != nil {
  446. beforeChildren(node, pos)
  447. }
  448. parent = node
  449. node = parent.firstChild
  450. return false
  451. }
  452. ac := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.CanvasObject) {
  453. node = parent
  454. parent = node.parent
  455. if prev != nil && prev.parent != parent {
  456. prev.nextSibling = nil
  457. }
  458. if afterChildren != nil {
  459. afterChildren(node, pos)
  460. }
  461. prev = node
  462. node = node.nextSibling
  463. }
  464. driver.WalkVisibleObjectTree(tree.root.obj, bc, ac)
  465. }
  466. // RenderCacheNode represents a node in a render cache tree.
  467. type RenderCacheNode struct {
  468. // structural data
  469. firstChild *RenderCacheNode
  470. nextSibling *RenderCacheNode
  471. obj fyne.CanvasObject
  472. parent *RenderCacheNode
  473. // cache data
  474. minSize fyne.Size
  475. // painterData is some data from the painter associated with the drawed node
  476. // it may for instance point to a GL texture
  477. // it should free all associated resources when released
  478. // i.e. it should not simply be a texture reference integer
  479. painterData interface{}
  480. }
  481. // Obj returns the node object.
  482. func (r *RenderCacheNode) Obj() fyne.CanvasObject {
  483. return r.obj
  484. }
  485. type activatableMenu interface {
  486. IsActive() bool
  487. }
  488. type overlayStack struct {
  489. internal.OverlayStack
  490. propertyLock sync.RWMutex
  491. renderCaches []*renderCacheTree
  492. }
  493. func (o *overlayStack) Add(overlay fyne.CanvasObject) {
  494. if overlay == nil {
  495. return
  496. }
  497. o.propertyLock.Lock()
  498. defer o.propertyLock.Unlock()
  499. o.add(overlay)
  500. }
  501. func (o *overlayStack) Remove(overlay fyne.CanvasObject) {
  502. if overlay == nil || len(o.List()) == 0 {
  503. return
  504. }
  505. o.propertyLock.Lock()
  506. defer o.propertyLock.Unlock()
  507. o.remove(overlay)
  508. }
  509. func (o *overlayStack) add(overlay fyne.CanvasObject) {
  510. o.renderCaches = append(o.renderCaches, &renderCacheTree{root: &RenderCacheNode{obj: overlay}})
  511. o.OverlayStack.Add(overlay)
  512. }
  513. func (o *overlayStack) remove(overlay fyne.CanvasObject) {
  514. o.OverlayStack.Remove(overlay)
  515. overlayCount := len(o.List())
  516. o.renderCaches[overlayCount] = nil // release memory reference to removed element
  517. o.renderCaches = o.renderCaches[:overlayCount]
  518. }
  519. type renderCacheTree struct {
  520. sync.RWMutex
  521. root *RenderCacheNode
  522. }
  523. func (c *Canvas) updateLayout(objToLayout fyne.CanvasObject) {
  524. switch cont := objToLayout.(type) {
  525. case *fyne.Container:
  526. if cont.Layout != nil {
  527. layout := cont.Layout
  528. objects := cont.Objects
  529. c.RUnlock()
  530. layout.Layout(objects, cont.Size())
  531. c.RLock()
  532. }
  533. case fyne.Widget:
  534. renderer := cache.Renderer(cont)
  535. c.RUnlock()
  536. renderer.Layout(cont.Size())
  537. c.RLock()
  538. }
  539. }