canvas.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  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. if c.painter != nil {
  250. cache.RangeExpiredTexturesFor(c.impl, c.painter.Free)
  251. }
  252. return
  253. }
  254. // Initialize initializes the canvas.
  255. func (c *Canvas) Initialize(impl SizeableCanvas, onOverlayChanged func()) {
  256. c.impl = impl
  257. c.refreshQueue = async.NewCanvasObjectQueue()
  258. c.overlays = &overlayStack{
  259. OverlayStack: internal.OverlayStack{
  260. OnChange: onOverlayChanged,
  261. Canvas: impl,
  262. },
  263. }
  264. }
  265. // ObjectTrees return canvas object trees.
  266. //
  267. // This function uses lock.
  268. func (c *Canvas) ObjectTrees() []fyne.CanvasObject {
  269. c.RLock()
  270. var content, menu fyne.CanvasObject
  271. if c.contentTree != nil && c.contentTree.root != nil {
  272. content = c.contentTree.root.obj
  273. }
  274. if c.menuTree != nil && c.menuTree.root != nil {
  275. menu = c.menuTree.root.obj
  276. }
  277. c.RUnlock()
  278. trees := make([]fyne.CanvasObject, 0, len(c.Overlays().List())+2)
  279. trees = append(trees, content)
  280. if menu != nil {
  281. trees = append(trees, menu)
  282. }
  283. trees = append(trees, c.Overlays().List()...)
  284. return trees
  285. }
  286. // Overlays returns the overlay stack.
  287. func (c *Canvas) Overlays() fyne.OverlayStack {
  288. // we don't need to lock here, because overlays never changes
  289. return c.overlays
  290. }
  291. // Painter returns the canvas painter.
  292. func (c *Canvas) Painter() gl.Painter {
  293. return c.painter
  294. }
  295. // Refresh refreshes a canvas object.
  296. func (c *Canvas) Refresh(obj fyne.CanvasObject) {
  297. walkNeeded := false
  298. switch obj.(type) {
  299. case *fyne.Container:
  300. walkNeeded = true
  301. case fyne.Widget:
  302. walkNeeded = true
  303. }
  304. if walkNeeded {
  305. driver.WalkCompleteObjectTree(obj, func(co fyne.CanvasObject, p1, p2 fyne.Position, s fyne.Size) bool {
  306. if i, ok := co.(*canvas.Image); ok {
  307. i.Refresh()
  308. }
  309. return false
  310. }, nil)
  311. }
  312. c.refreshQueue.In(obj)
  313. c.SetDirty()
  314. }
  315. // RemoveShortcut removes a shortcut from the canvas.
  316. func (c *Canvas) RemoveShortcut(shortcut fyne.Shortcut) {
  317. c.shortcut.RemoveShortcut(shortcut)
  318. }
  319. // SetContentTreeAndFocusMgr sets content tree and focus manager.
  320. //
  321. // This function does not use the canvas lock.
  322. func (c *Canvas) SetContentTreeAndFocusMgr(content fyne.CanvasObject) {
  323. c.contentTree = &renderCacheTree{root: &RenderCacheNode{obj: content}}
  324. var focused fyne.Focusable
  325. if c.contentFocusMgr != nil {
  326. focused = c.contentFocusMgr.Focused() // keep old focus if possible
  327. }
  328. c.contentFocusMgr = app.NewFocusManager(content)
  329. if focused != nil {
  330. c.contentFocusMgr.Focus(focused)
  331. }
  332. }
  333. // CheckDirtyAndClear returns true if the canvas is dirty and
  334. // clears the dirty state atomically.
  335. func (c *Canvas) CheckDirtyAndClear() bool {
  336. return atomic.SwapUint32(&c.dirty, 0) != 0
  337. }
  338. // SetDirty sets canvas dirty flag atomically.
  339. func (c *Canvas) SetDirty() {
  340. atomic.AddUint32(&c.dirty, 1)
  341. }
  342. // SetMenuTreeAndFocusMgr sets menu tree and focus manager.
  343. //
  344. // This function does not use the canvas lock.
  345. func (c *Canvas) SetMenuTreeAndFocusMgr(menu fyne.CanvasObject) {
  346. c.menuTree = &renderCacheTree{root: &RenderCacheNode{obj: menu}}
  347. if menu != nil {
  348. c.menuFocusMgr = app.NewFocusManager(menu)
  349. } else {
  350. c.menuFocusMgr = nil
  351. }
  352. }
  353. // SetMobileWindowHeadTree sets window head tree.
  354. //
  355. // This function does not use the canvas lock.
  356. func (c *Canvas) SetMobileWindowHeadTree(head fyne.CanvasObject) {
  357. c.mWindowHeadTree = &renderCacheTree{root: &RenderCacheNode{obj: head}}
  358. }
  359. // SetPainter sets the canvas painter.
  360. func (c *Canvas) SetPainter(p gl.Painter) {
  361. c.painter = p
  362. }
  363. // TypedShortcut handle the registered shortcut.
  364. func (c *Canvas) TypedShortcut(shortcut fyne.Shortcut) {
  365. c.shortcut.TypedShortcut(shortcut)
  366. }
  367. // Unfocus unfocuses all the objects in the canvas.
  368. func (c *Canvas) Unfocus() {
  369. mgr := c.focusManager()
  370. if mgr == nil {
  371. return
  372. }
  373. if mgr.Focus(nil) && c.OnUnfocus != nil {
  374. c.OnUnfocus()
  375. }
  376. }
  377. // WalkTrees walks over the trees.
  378. func (c *Canvas) WalkTrees(
  379. beforeChildren func(*RenderCacheNode, fyne.Position),
  380. afterChildren func(*RenderCacheNode, fyne.Position),
  381. ) {
  382. c.walkTree(c.contentTree, beforeChildren, afterChildren)
  383. if c.mWindowHeadTree != nil && c.mWindowHeadTree.root.obj != nil {
  384. c.walkTree(c.mWindowHeadTree, beforeChildren, afterChildren)
  385. }
  386. if c.menuTree != nil && c.menuTree.root.obj != nil {
  387. c.walkTree(c.menuTree, beforeChildren, afterChildren)
  388. }
  389. for _, tree := range c.overlays.renderCaches {
  390. if tree != nil {
  391. c.walkTree(tree, beforeChildren, afterChildren)
  392. }
  393. }
  394. }
  395. func (c *Canvas) focusManager() *app.FocusManager {
  396. if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
  397. return focusMgr
  398. }
  399. c.RLock()
  400. defer c.RUnlock()
  401. if c.isMenuActive() {
  402. return c.menuFocusMgr
  403. }
  404. return c.contentFocusMgr
  405. }
  406. func (c *Canvas) isMenuActive() bool {
  407. if c.menuTree == nil || c.menuTree.root == nil || c.menuTree.root.obj == nil {
  408. return false
  409. }
  410. menu := c.menuTree.root.obj
  411. if am, ok := menu.(activatableMenu); ok {
  412. return am.IsActive()
  413. }
  414. return true
  415. }
  416. func (c *Canvas) walkTree(
  417. tree *renderCacheTree,
  418. beforeChildren func(*RenderCacheNode, fyne.Position),
  419. afterChildren func(*RenderCacheNode, fyne.Position),
  420. ) {
  421. tree.Lock()
  422. defer tree.Unlock()
  423. var node, parent, prev *RenderCacheNode
  424. node = tree.root
  425. bc := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.Position, _ fyne.Size) bool {
  426. if node != nil && node.obj != obj {
  427. if parent.firstChild == node {
  428. parent.firstChild = nil
  429. }
  430. node = nil
  431. }
  432. if node == nil {
  433. node = &RenderCacheNode{parent: parent, obj: obj}
  434. if parent.firstChild == nil {
  435. parent.firstChild = node
  436. } else {
  437. prev.nextSibling = node
  438. }
  439. }
  440. if prev != nil && prev.parent != parent {
  441. prev = nil
  442. }
  443. if beforeChildren != nil {
  444. beforeChildren(node, pos)
  445. }
  446. parent = node
  447. node = parent.firstChild
  448. return false
  449. }
  450. ac := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.CanvasObject) {
  451. node = parent
  452. parent = node.parent
  453. if prev != nil && prev.parent != parent {
  454. prev.nextSibling = nil
  455. }
  456. if afterChildren != nil {
  457. afterChildren(node, pos)
  458. }
  459. prev = node
  460. node = node.nextSibling
  461. }
  462. driver.WalkVisibleObjectTree(tree.root.obj, bc, ac)
  463. }
  464. // RenderCacheNode represents a node in a render cache tree.
  465. type RenderCacheNode struct {
  466. // structural data
  467. firstChild *RenderCacheNode
  468. nextSibling *RenderCacheNode
  469. obj fyne.CanvasObject
  470. parent *RenderCacheNode
  471. // cache data
  472. minSize fyne.Size
  473. // painterData is some data from the painter associated with the drawed node
  474. // it may for instance point to a GL texture
  475. // it should free all associated resources when released
  476. // i.e. it should not simply be a texture reference integer
  477. painterData interface{}
  478. }
  479. // Obj returns the node object.
  480. func (r *RenderCacheNode) Obj() fyne.CanvasObject {
  481. return r.obj
  482. }
  483. type activatableMenu interface {
  484. IsActive() bool
  485. }
  486. type overlayStack struct {
  487. internal.OverlayStack
  488. propertyLock sync.RWMutex
  489. renderCaches []*renderCacheTree
  490. }
  491. func (o *overlayStack) Add(overlay fyne.CanvasObject) {
  492. if overlay == nil {
  493. return
  494. }
  495. o.propertyLock.Lock()
  496. defer o.propertyLock.Unlock()
  497. o.add(overlay)
  498. }
  499. func (o *overlayStack) Remove(overlay fyne.CanvasObject) {
  500. if overlay == nil || len(o.List()) == 0 {
  501. return
  502. }
  503. o.propertyLock.Lock()
  504. defer o.propertyLock.Unlock()
  505. o.remove(overlay)
  506. }
  507. func (o *overlayStack) add(overlay fyne.CanvasObject) {
  508. o.renderCaches = append(o.renderCaches, &renderCacheTree{root: &RenderCacheNode{obj: overlay}})
  509. o.OverlayStack.Add(overlay)
  510. }
  511. func (o *overlayStack) remove(overlay fyne.CanvasObject) {
  512. o.OverlayStack.Remove(overlay)
  513. overlayCount := len(o.List())
  514. o.renderCaches[overlayCount] = nil // release memory reference to removed element
  515. o.renderCaches = o.renderCaches[:overlayCount]
  516. }
  517. type renderCacheTree struct {
  518. sync.RWMutex
  519. root *RenderCacheNode
  520. }
  521. func (c *Canvas) updateLayout(objToLayout fyne.CanvasObject) {
  522. switch cont := objToLayout.(type) {
  523. case *fyne.Container:
  524. if cont.Layout != nil {
  525. layout := cont.Layout
  526. objects := cont.Objects
  527. c.RUnlock()
  528. layout.Layout(objects, cont.Size())
  529. c.RLock()
  530. }
  531. case fyne.Widget:
  532. renderer := cache.Renderer(cont)
  533. c.RUnlock()
  534. renderer.Layout(cont.Size())
  535. c.RLock()
  536. }
  537. }