tree.go 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. package widget
  2. import (
  3. "fmt"
  4. "fyne.io/fyne/v2"
  5. "fyne.io/fyne/v2/canvas"
  6. "fyne.io/fyne/v2/data/binding"
  7. "fyne.io/fyne/v2/driver/desktop"
  8. "fyne.io/fyne/v2/internal/cache"
  9. "fyne.io/fyne/v2/internal/widget"
  10. "fyne.io/fyne/v2/theme"
  11. )
  12. // allTreeNodesID represents all tree nodes when refreshing requested nodes
  13. const allTreeNodesID = "_ALLNODES"
  14. // TreeNodeID represents the unique id of a tree node.
  15. type TreeNodeID = string
  16. // Declare conformity with interfaces
  17. var _ fyne.Focusable = (*Tree)(nil)
  18. var _ fyne.Widget = (*Tree)(nil)
  19. // Tree widget displays hierarchical data.
  20. // Each node of the tree must be identified by a Unique TreeNodeID.
  21. //
  22. // Since: 1.4
  23. type Tree struct {
  24. BaseWidget
  25. Root TreeNodeID
  26. ChildUIDs func(uid TreeNodeID) (c []TreeNodeID) `json:"-"` // Return a sorted slice of Children TreeNodeIDs for the given Node TreeNodeID
  27. CreateNode func(branch bool) (o fyne.CanvasObject) `json:"-"` // Return a CanvasObject that can represent a Branch (if branch is true), or a Leaf (if branch is false)
  28. IsBranch func(uid TreeNodeID) (ok bool) `json:"-"` // Return true if the given TreeNodeID represents a Branch
  29. OnBranchClosed func(uid TreeNodeID) `json:"-"` // Called when a Branch is closed
  30. OnBranchOpened func(uid TreeNodeID) `json:"-"` // Called when a Branch is opened
  31. OnSelected func(uid TreeNodeID) `json:"-"` // Called when the Node with the given TreeNodeID is selected.
  32. OnUnselected func(uid TreeNodeID) `json:"-"` // Called when the Node with the given TreeNodeID is unselected.
  33. UpdateNode func(uid TreeNodeID, branch bool, node fyne.CanvasObject) `json:"-"` // Called to update the given CanvasObject to represent the data at the given TreeNodeID
  34. branchMinSize fyne.Size
  35. currentFocus TreeNodeID
  36. focused bool
  37. leafMinSize fyne.Size
  38. offset fyne.Position
  39. open map[TreeNodeID]bool
  40. scroller *widget.Scroll
  41. selected []TreeNodeID
  42. }
  43. // NewTree returns a new performant tree widget defined by the passed functions.
  44. // childUIDs returns the child TreeNodeIDs of the given node.
  45. // isBranch returns true if the given node is a branch, false if it is a leaf.
  46. // create returns a new template object that can be cached.
  47. // update is used to apply data at specified data location to the passed template CanvasObject.
  48. //
  49. // Since: 1.4
  50. func NewTree(childUIDs func(TreeNodeID) []TreeNodeID, isBranch func(TreeNodeID) bool, create func(bool) fyne.CanvasObject, update func(TreeNodeID, bool, fyne.CanvasObject)) *Tree {
  51. t := &Tree{ChildUIDs: childUIDs, IsBranch: isBranch, CreateNode: create, UpdateNode: update}
  52. t.ExtendBaseWidget(t)
  53. return t
  54. }
  55. // NewTreeWithData creates a new tree widget that will display the contents of the provided data.
  56. //
  57. // Since: 2.4
  58. func NewTreeWithData(data binding.DataTree, createItem func(bool) fyne.CanvasObject, updateItem func(binding.DataItem, bool, fyne.CanvasObject)) *Tree {
  59. t := NewTree(
  60. data.ChildIDs,
  61. func(id TreeNodeID) bool {
  62. children := data.ChildIDs(id)
  63. return len(children) > 0
  64. },
  65. createItem,
  66. func(i TreeNodeID, branch bool, o fyne.CanvasObject) {
  67. item, err := data.GetItem(i)
  68. if err != nil {
  69. fyne.LogError(fmt.Sprintf("Error getting data item %s", i), err)
  70. return
  71. }
  72. updateItem(item, branch, o)
  73. })
  74. data.AddListener(binding.NewDataListener(t.Refresh))
  75. return t
  76. }
  77. // NewTreeWithStrings creates a new tree with the given string map.
  78. // Data must contain a mapping for the root, which defaults to empty string ("").
  79. //
  80. // Since: 1.4
  81. func NewTreeWithStrings(data map[string][]string) (t *Tree) {
  82. t = &Tree{
  83. ChildUIDs: func(uid string) (c []string) {
  84. c = data[uid]
  85. return
  86. },
  87. IsBranch: func(uid string) (b bool) {
  88. _, b = data[uid]
  89. return
  90. },
  91. CreateNode: func(branch bool) fyne.CanvasObject {
  92. return NewLabel("Template Object")
  93. },
  94. UpdateNode: func(uid string, branch bool, node fyne.CanvasObject) {
  95. node.(*Label).SetText(uid)
  96. },
  97. }
  98. t.ExtendBaseWidget(t)
  99. return
  100. }
  101. // CloseAllBranches closes all branches in the tree.
  102. func (t *Tree) CloseAllBranches() {
  103. t.propertyLock.Lock()
  104. t.open = make(map[TreeNodeID]bool)
  105. t.propertyLock.Unlock()
  106. t.Refresh()
  107. }
  108. // CloseBranch closes the branch with the given TreeNodeID.
  109. func (t *Tree) CloseBranch(uid TreeNodeID) {
  110. t.ensureOpenMap()
  111. t.propertyLock.Lock()
  112. t.open[uid] = false
  113. t.propertyLock.Unlock()
  114. if f := t.OnBranchClosed; f != nil {
  115. f(uid)
  116. }
  117. t.Refresh()
  118. }
  119. // CreateRenderer is a private method to Fyne which links this widget to its renderer.
  120. func (t *Tree) CreateRenderer() fyne.WidgetRenderer {
  121. t.ExtendBaseWidget(t)
  122. c := newTreeContent(t)
  123. s := widget.NewScroll(c)
  124. t.scroller = s
  125. r := &treeRenderer{
  126. BaseRenderer: widget.NewBaseRenderer([]fyne.CanvasObject{s}),
  127. tree: t,
  128. content: c,
  129. scroller: s,
  130. }
  131. s.OnScrolled = t.offsetUpdated
  132. r.updateMinSizes()
  133. r.content.viewport = r.MinSize()
  134. return r
  135. }
  136. // IsBranchOpen returns true if the branch with the given TreeNodeID is expanded.
  137. func (t *Tree) IsBranchOpen(uid TreeNodeID) bool {
  138. if uid == t.Root {
  139. return true // Root is always open
  140. }
  141. t.ensureOpenMap()
  142. t.propertyLock.RLock()
  143. defer t.propertyLock.RUnlock()
  144. return t.open[uid]
  145. }
  146. // FocusGained is called after this Tree has gained focus.
  147. //
  148. // Implements: fyne.Focusable
  149. func (t *Tree) FocusGained() {
  150. if t.currentFocus == "" {
  151. if childUIDs := t.ChildUIDs; childUIDs != nil {
  152. if ids := childUIDs(""); len(ids) > 0 {
  153. t.currentFocus = ids[0]
  154. }
  155. }
  156. }
  157. t.focused = true
  158. t.ScrollTo(t.currentFocus)
  159. t.RefreshItem(t.currentFocus)
  160. }
  161. // FocusLost is called after this Tree has lost focus.
  162. //
  163. // Implements: fyne.Focusable
  164. func (t *Tree) FocusLost() {
  165. t.focused = false
  166. t.Refresh() //Item(t.currentFocus)
  167. }
  168. // MinSize returns the size that this widget should not shrink below.
  169. func (t *Tree) MinSize() fyne.Size {
  170. t.ExtendBaseWidget(t)
  171. return t.BaseWidget.MinSize()
  172. }
  173. // RefreshItem refreshes a single item, specified by the item ID passed in.
  174. //
  175. // Since: 2.4
  176. func (t *Tree) RefreshItem(id TreeNodeID) {
  177. if t.scroller == nil {
  178. return
  179. }
  180. r := cache.Renderer(t.scroller.Content.(*treeContent))
  181. if r == nil {
  182. return
  183. }
  184. r.(*treeContentRenderer).refreshForID(id)
  185. }
  186. // OpenAllBranches opens all branches in the tree.
  187. func (t *Tree) OpenAllBranches() {
  188. t.ensureOpenMap()
  189. t.walkAll(func(uid, parent TreeNodeID, branch bool, depth int) {
  190. if branch {
  191. t.propertyLock.Lock()
  192. t.open[uid] = true
  193. t.propertyLock.Unlock()
  194. }
  195. })
  196. t.Refresh()
  197. }
  198. // OpenBranch opens the branch with the given TreeNodeID.
  199. func (t *Tree) OpenBranch(uid TreeNodeID) {
  200. t.ensureOpenMap()
  201. t.propertyLock.Lock()
  202. t.open[uid] = true
  203. t.propertyLock.Unlock()
  204. if f := t.OnBranchOpened; f != nil {
  205. f(uid)
  206. }
  207. t.Refresh()
  208. }
  209. // Resize sets a new size for a widget.
  210. func (t *Tree) Resize(size fyne.Size) {
  211. t.propertyLock.RLock()
  212. s := t.size
  213. t.propertyLock.RUnlock()
  214. if s == size {
  215. return
  216. }
  217. t.propertyLock.Lock()
  218. t.size = size
  219. t.propertyLock.Unlock()
  220. t.Refresh() // trigger a redraw
  221. }
  222. // ScrollToBottom scrolls to the bottom of the tree.
  223. //
  224. // Since 2.1
  225. func (t *Tree) ScrollToBottom() {
  226. if t.scroller == nil {
  227. return
  228. }
  229. y, size := t.findBottom()
  230. t.scroller.Offset.Y = y + size.Height - t.scroller.Size().Height
  231. t.offsetUpdated(t.scroller.Offset)
  232. t.Refresh()
  233. }
  234. // ScrollTo scrolls to the node with the given id.
  235. //
  236. // Since 2.1
  237. func (t *Tree) ScrollTo(uid TreeNodeID) {
  238. if t.scroller == nil {
  239. return
  240. }
  241. y, size, ok := t.offsetAndSize(uid)
  242. if !ok {
  243. return
  244. }
  245. // TODO scrolling to a node should open all parents if they aren't already
  246. if y < t.scroller.Offset.Y {
  247. t.scroller.Offset.Y = y
  248. } else if y+size.Height > t.scroller.Offset.Y+t.scroller.Size().Height {
  249. t.scroller.Offset.Y = y + size.Height - t.scroller.Size().Height
  250. }
  251. t.offsetUpdated(t.scroller.Offset)
  252. t.Refresh()
  253. }
  254. // ScrollToTop scrolls to the top of the tree.
  255. //
  256. // Since 2.1
  257. func (t *Tree) ScrollToTop() {
  258. if t.scroller == nil {
  259. return
  260. }
  261. t.scroller.Offset.Y = 0
  262. t.offsetUpdated(t.scroller.Offset)
  263. t.Refresh()
  264. }
  265. // Select marks the specified node to be selected.
  266. func (t *Tree) Select(uid TreeNodeID) {
  267. if len(t.selected) > 0 {
  268. if uid == t.selected[0] {
  269. return // no change
  270. }
  271. if f := t.OnUnselected; f != nil {
  272. f(t.selected[0])
  273. }
  274. }
  275. t.selected = []TreeNodeID{uid}
  276. t.ScrollTo(uid)
  277. if f := t.OnSelected; f != nil {
  278. f(uid)
  279. }
  280. }
  281. // ToggleBranch flips the state of the branch with the given TreeNodeID.
  282. func (t *Tree) ToggleBranch(uid string) {
  283. if t.IsBranchOpen(uid) {
  284. t.CloseBranch(uid)
  285. } else {
  286. t.OpenBranch(uid)
  287. }
  288. }
  289. // TypedKey is called if a key event happens while this Tree is focused.
  290. //
  291. // Implements: fyne.Focusable
  292. func (t *Tree) TypedKey(event *fyne.KeyEvent) {
  293. switch event.Name {
  294. case fyne.KeySpace:
  295. t.Select(t.currentFocus)
  296. case fyne.KeyDown:
  297. t.RefreshItem(t.currentFocus)
  298. next := false
  299. t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
  300. if next {
  301. t.currentFocus = id
  302. next = false
  303. } else if id == t.currentFocus {
  304. next = true
  305. }
  306. })
  307. t.ScrollTo(t.currentFocus)
  308. t.RefreshItem(t.currentFocus)
  309. case fyne.KeyLeft:
  310. // If the current focus is on a branch which is open, just close it
  311. if t.IsBranch(t.currentFocus) && t.IsBranchOpen(t.currentFocus) {
  312. t.CloseBranch(t.currentFocus)
  313. } else {
  314. // Every other case should move the focus to the current parent node
  315. t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
  316. if id == t.currentFocus && p != "" {
  317. t.currentFocus = p
  318. }
  319. })
  320. }
  321. t.RefreshItem(t.currentFocus)
  322. t.ScrollTo(t.currentFocus)
  323. t.RefreshItem(t.currentFocus)
  324. case fyne.KeyRight:
  325. if t.IsBranch(t.currentFocus) {
  326. t.OpenBranch(t.currentFocus)
  327. }
  328. children := []TreeNodeID{}
  329. if childUIDs := t.ChildUIDs; childUIDs != nil {
  330. children = childUIDs(t.currentFocus)
  331. }
  332. if len(children) > 0 {
  333. t.currentFocus = children[0]
  334. }
  335. t.RefreshItem(t.currentFocus)
  336. t.ScrollTo(t.currentFocus)
  337. t.RefreshItem(t.currentFocus)
  338. case fyne.KeyUp:
  339. t.RefreshItem(t.currentFocus)
  340. previous := ""
  341. t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
  342. if id == t.currentFocus && previous != "" {
  343. t.currentFocus = previous
  344. }
  345. previous = id
  346. })
  347. t.ScrollTo(t.currentFocus)
  348. t.RefreshItem(t.currentFocus)
  349. }
  350. }
  351. // TypedRune is called if a text event happens while this Tree is focused.
  352. //
  353. // Implements: fyne.Focusable
  354. func (t *Tree) TypedRune(_ rune) {
  355. // intentionally left blank
  356. }
  357. // Unselect marks the specified node to be not selected.
  358. func (t *Tree) Unselect(uid TreeNodeID) {
  359. if len(t.selected) == 0 || t.selected[0] != uid {
  360. return
  361. }
  362. t.selected = nil
  363. t.Refresh()
  364. if f := t.OnUnselected; f != nil {
  365. f(uid)
  366. }
  367. }
  368. // UnselectAll sets all nodes to be not selected.
  369. //
  370. // Since: 2.1
  371. func (t *Tree) UnselectAll() {
  372. if len(t.selected) == 0 {
  373. return
  374. }
  375. selected := t.selected
  376. t.selected = nil
  377. t.Refresh()
  378. if f := t.OnUnselected; f != nil {
  379. for _, uid := range selected {
  380. f(uid)
  381. }
  382. }
  383. }
  384. func (t *Tree) ensureOpenMap() {
  385. t.propertyLock.Lock()
  386. defer t.propertyLock.Unlock()
  387. if t.open == nil {
  388. t.open = make(map[string]bool)
  389. }
  390. }
  391. func (t *Tree) findBottom() (y float32, size fyne.Size) {
  392. sep := theme.Padding()
  393. t.walkAll(func(id, _ TreeNodeID, branch bool, _ int) {
  394. size = t.leafMinSize
  395. if branch {
  396. size = t.branchMinSize
  397. }
  398. // Root node is not rendered unless it has been customized
  399. if t.Root == "" && id == "" {
  400. // This is root node, skip
  401. return
  402. }
  403. // If this is not the first item, add a separator
  404. if y > 0 {
  405. y += sep
  406. }
  407. y += size.Height
  408. })
  409. if y > 0 {
  410. y -= sep
  411. }
  412. return
  413. }
  414. func (t *Tree) offsetAndSize(uid TreeNodeID) (y float32, size fyne.Size, found bool) {
  415. t.walkAll(func(id, _ TreeNodeID, branch bool, _ int) {
  416. m := t.leafMinSize
  417. if branch {
  418. m = t.branchMinSize
  419. }
  420. if id == uid {
  421. found = true
  422. size = m
  423. } else if !found {
  424. // Root node is not rendered unless it has been customized
  425. if t.Root == "" && id == "" {
  426. // This is root node, skip
  427. return
  428. }
  429. // If this is not the first item, add a separator
  430. if y > 0 {
  431. y += theme.Padding()
  432. }
  433. y += m.Height
  434. }
  435. })
  436. return
  437. }
  438. func (t *Tree) offsetUpdated(pos fyne.Position) {
  439. if t.offset == pos {
  440. return
  441. }
  442. t.offset = pos
  443. t.scroller.Content.Refresh()
  444. }
  445. func (t *Tree) walk(uid, parent TreeNodeID, depth int, onNode func(TreeNodeID, TreeNodeID, bool, int)) {
  446. if isBranch := t.IsBranch; isBranch != nil {
  447. if isBranch(uid) {
  448. onNode(uid, parent, true, depth)
  449. if t.IsBranchOpen(uid) {
  450. if childUIDs := t.ChildUIDs; childUIDs != nil {
  451. for _, c := range childUIDs(uid) {
  452. t.walk(c, uid, depth+1, onNode)
  453. }
  454. }
  455. }
  456. } else {
  457. onNode(uid, parent, false, depth)
  458. }
  459. }
  460. }
  461. // walkAll visits every open node of the tree and calls the given callback with TreeNodeID, whether node is branch, and the depth of node.
  462. func (t *Tree) walkAll(onNode func(TreeNodeID, TreeNodeID, bool, int)) {
  463. t.walk(t.Root, "", 0, onNode)
  464. }
  465. var _ fyne.WidgetRenderer = (*treeRenderer)(nil)
  466. type treeRenderer struct {
  467. widget.BaseRenderer
  468. tree *Tree
  469. content *treeContent
  470. scroller *widget.Scroll
  471. }
  472. func (r *treeRenderer) MinSize() (min fyne.Size) {
  473. min = r.scroller.MinSize()
  474. min = min.Max(r.tree.branchMinSize)
  475. min = min.Max(r.tree.leafMinSize)
  476. return
  477. }
  478. func (r *treeRenderer) Layout(size fyne.Size) {
  479. r.content.viewport = size
  480. r.scroller.Resize(size)
  481. }
  482. func (r *treeRenderer) Refresh() {
  483. r.updateMinSizes()
  484. s := r.tree.Size()
  485. if s.IsZero() {
  486. r.tree.Resize(r.tree.MinSize())
  487. } else {
  488. r.Layout(s)
  489. }
  490. r.scroller.Refresh()
  491. r.content.Refresh()
  492. canvas.Refresh(r.tree.super())
  493. }
  494. func (r *treeRenderer) updateMinSizes() {
  495. if f := r.tree.CreateNode; f != nil {
  496. r.tree.branchMinSize = newBranch(r.tree, f(true)).MinSize()
  497. r.tree.leafMinSize = newLeaf(r.tree, f(false)).MinSize()
  498. }
  499. }
  500. var _ fyne.Widget = (*treeContent)(nil)
  501. type treeContent struct {
  502. BaseWidget
  503. tree *Tree
  504. viewport fyne.Size
  505. }
  506. func newTreeContent(tree *Tree) (c *treeContent) {
  507. c = &treeContent{
  508. tree: tree,
  509. }
  510. c.ExtendBaseWidget(c)
  511. return
  512. }
  513. func (c *treeContent) CreateRenderer() fyne.WidgetRenderer {
  514. return &treeContentRenderer{
  515. BaseRenderer: widget.BaseRenderer{},
  516. treeContent: c,
  517. branches: make(map[string]*branch),
  518. leaves: make(map[string]*leaf),
  519. branchPool: &syncPool{},
  520. leafPool: &syncPool{},
  521. }
  522. }
  523. func (c *treeContent) Resize(size fyne.Size) {
  524. c.propertyLock.RLock()
  525. s := c.size
  526. c.propertyLock.RUnlock()
  527. if s == size {
  528. return
  529. }
  530. c.propertyLock.Lock()
  531. c.size = size
  532. c.propertyLock.Unlock()
  533. c.Refresh() // trigger a redraw
  534. }
  535. var _ fyne.WidgetRenderer = (*treeContentRenderer)(nil)
  536. type treeContentRenderer struct {
  537. widget.BaseRenderer
  538. treeContent *treeContent
  539. separators []fyne.CanvasObject
  540. objects []fyne.CanvasObject
  541. branches map[string]*branch
  542. leaves map[string]*leaf
  543. branchPool pool
  544. leafPool pool
  545. }
  546. func (r *treeContentRenderer) Layout(size fyne.Size) {
  547. r.treeContent.propertyLock.Lock()
  548. defer r.treeContent.propertyLock.Unlock()
  549. r.objects = nil
  550. branches := make(map[string]*branch)
  551. leaves := make(map[string]*leaf)
  552. pad := theme.Padding()
  553. offsetY := r.treeContent.tree.offset.Y
  554. viewport := r.treeContent.viewport
  555. width := fyne.Max(size.Width, viewport.Width)
  556. separatorCount := 0
  557. separatorThickness := theme.SeparatorThicknessSize()
  558. separatorSize := fyne.NewSize(width, separatorThickness)
  559. separatorOff := (pad + separatorThickness) / 2
  560. y := float32(0)
  561. // walkAll open branches and obtain nodes to render in scroller's viewport
  562. r.treeContent.tree.walkAll(func(uid, _ string, isBranch bool, depth int) {
  563. // Root node is not rendered unless it has been customized
  564. if r.treeContent.tree.Root == "" {
  565. depth = depth - 1
  566. if uid == "" {
  567. // This is root node, skip
  568. return
  569. }
  570. }
  571. // If this is not the first item, add a separator
  572. addSeparator := y > 0
  573. if addSeparator {
  574. y += pad
  575. separatorCount++
  576. }
  577. m := r.treeContent.tree.leafMinSize
  578. if isBranch {
  579. m = r.treeContent.tree.branchMinSize
  580. }
  581. if y+m.Height < offsetY {
  582. // Node is above viewport and not visible
  583. } else if y > offsetY+viewport.Height {
  584. // Node is below viewport and not visible
  585. } else {
  586. // Node is in viewport
  587. if addSeparator {
  588. var separator fyne.CanvasObject
  589. if separatorCount < len(r.separators) {
  590. separator = r.separators[separatorCount]
  591. } else {
  592. separator = NewSeparator()
  593. r.separators = append(r.separators, separator)
  594. }
  595. separator.Move(fyne.NewPos(0, y-separatorOff))
  596. separator.Resize(separatorSize)
  597. r.objects = append(r.objects, separator)
  598. separatorCount++
  599. }
  600. var n fyne.CanvasObject
  601. if isBranch {
  602. b, ok := r.branches[uid]
  603. if !ok {
  604. b = r.getBranch()
  605. if f := r.treeContent.tree.UpdateNode; f != nil {
  606. f(uid, true, b.Content())
  607. }
  608. b.update(uid, depth)
  609. }
  610. branches[uid] = b
  611. n = b
  612. r.objects = append(r.objects, b)
  613. } else {
  614. l, ok := r.leaves[uid]
  615. if !ok {
  616. l = r.getLeaf()
  617. if f := r.treeContent.tree.UpdateNode; f != nil {
  618. f(uid, false, l.Content())
  619. }
  620. l.update(uid, depth)
  621. }
  622. leaves[uid] = l
  623. n = l
  624. r.objects = append(r.objects, l)
  625. }
  626. if n != nil {
  627. n.Move(fyne.NewPos(0, y))
  628. n.Resize(fyne.NewSize(width, m.Height))
  629. }
  630. }
  631. y += m.Height
  632. })
  633. // Hide any separators that haven't been reused
  634. for ; separatorCount < len(r.separators); separatorCount++ {
  635. r.separators[separatorCount].Hide()
  636. }
  637. // Release any nodes that haven't been reused
  638. for uid, b := range r.branches {
  639. if _, ok := branches[uid]; !ok {
  640. r.branchPool.Release(b)
  641. }
  642. }
  643. for uid, l := range r.leaves {
  644. if _, ok := leaves[uid]; !ok {
  645. r.leafPool.Release(l)
  646. }
  647. }
  648. r.branches = branches
  649. r.leaves = leaves
  650. }
  651. func (r *treeContentRenderer) MinSize() (min fyne.Size) {
  652. r.treeContent.propertyLock.Lock()
  653. defer r.treeContent.propertyLock.Unlock()
  654. r.treeContent.tree.walkAll(func(uid, _ string, isBranch bool, depth int) {
  655. // Root node is not rendered unless it has been customized
  656. if r.treeContent.tree.Root == "" {
  657. depth = depth - 1
  658. if uid == "" {
  659. // This is root node, skip
  660. return
  661. }
  662. }
  663. // If this is not the first item, add a separator
  664. if min.Height > 0 {
  665. min.Height += theme.Padding()
  666. }
  667. m := r.treeContent.tree.leafMinSize
  668. if isBranch {
  669. m = r.treeContent.tree.branchMinSize
  670. }
  671. m.Width += float32(depth) * (theme.IconInlineSize() + theme.Padding())
  672. min.Width = fyne.Max(min.Width, m.Width)
  673. min.Height += m.Height
  674. })
  675. return
  676. }
  677. func (r *treeContentRenderer) Objects() []fyne.CanvasObject {
  678. return r.objects
  679. }
  680. func (r *treeContentRenderer) Refresh() {
  681. r.refreshForID(allTreeNodesID)
  682. }
  683. func (r *treeContentRenderer) refreshForID(toDraw TreeNodeID) {
  684. s := r.treeContent.Size()
  685. if s.IsZero() {
  686. r.treeContent.Resize(r.treeContent.MinSize().Max(r.treeContent.tree.Size()))
  687. } else {
  688. r.Layout(s)
  689. }
  690. r.treeContent.propertyLock.RLock()
  691. for id, b := range r.branches {
  692. if toDraw != allTreeNodesID && id != toDraw {
  693. continue
  694. }
  695. b.Refresh()
  696. }
  697. for id, l := range r.leaves {
  698. if toDraw != allTreeNodesID && id != toDraw {
  699. continue
  700. }
  701. l.Refresh()
  702. }
  703. r.treeContent.propertyLock.RUnlock()
  704. canvas.Refresh(r.treeContent.super())
  705. }
  706. func (r *treeContentRenderer) getBranch() (b *branch) {
  707. o := r.branchPool.Obtain()
  708. if o != nil {
  709. b = o.(*branch)
  710. } else {
  711. var content fyne.CanvasObject
  712. if f := r.treeContent.tree.CreateNode; f != nil {
  713. content = f(true)
  714. }
  715. b = newBranch(r.treeContent.tree, content)
  716. }
  717. return
  718. }
  719. func (r *treeContentRenderer) getLeaf() (l *leaf) {
  720. o := r.leafPool.Obtain()
  721. if o != nil {
  722. l = o.(*leaf)
  723. } else {
  724. var content fyne.CanvasObject
  725. if f := r.treeContent.tree.CreateNode; f != nil {
  726. content = f(false)
  727. }
  728. l = newLeaf(r.treeContent.tree, content)
  729. }
  730. return
  731. }
  732. var _ desktop.Hoverable = (*treeNode)(nil)
  733. var _ fyne.CanvasObject = (*treeNode)(nil)
  734. var _ fyne.Tappable = (*treeNode)(nil)
  735. type treeNode struct {
  736. BaseWidget
  737. tree *Tree
  738. uid string
  739. depth int
  740. hovered bool
  741. icon fyne.CanvasObject
  742. isBranch bool
  743. content fyne.CanvasObject
  744. }
  745. func (n *treeNode) Content() fyne.CanvasObject {
  746. return n.content
  747. }
  748. func (n *treeNode) CreateRenderer() fyne.WidgetRenderer {
  749. background := canvas.NewRectangle(theme.HoverColor())
  750. background.CornerRadius = theme.SelectionRadiusSize()
  751. background.Hide()
  752. return &treeNodeRenderer{
  753. BaseRenderer: widget.BaseRenderer{},
  754. treeNode: n,
  755. background: background,
  756. }
  757. }
  758. func (n *treeNode) Indent() float32 {
  759. return float32(n.depth) * (theme.IconInlineSize() + theme.Padding())
  760. }
  761. // MouseIn is called when a desktop pointer enters the widget
  762. func (n *treeNode) MouseIn(*desktop.MouseEvent) {
  763. n.hovered = true
  764. n.partialRefresh()
  765. }
  766. // MouseMoved is called when a desktop pointer hovers over the widget
  767. func (n *treeNode) MouseMoved(*desktop.MouseEvent) {
  768. }
  769. // MouseOut is called when a desktop pointer exits the widget
  770. func (n *treeNode) MouseOut() {
  771. n.hovered = false
  772. n.partialRefresh()
  773. }
  774. func (n *treeNode) Tapped(*fyne.PointEvent) {
  775. if n.tree.currentFocus != "" {
  776. n.tree.RefreshItem(n.tree.currentFocus)
  777. }
  778. n.tree.Select(n.uid)
  779. if !fyne.CurrentDevice().IsMobile() {
  780. canvas := fyne.CurrentApp().Driver().CanvasForObject(n.tree)
  781. if canvas != nil {
  782. canvas.Focus(n.tree)
  783. }
  784. n.tree.currentFocus = n.uid
  785. n.Refresh()
  786. }
  787. }
  788. func (n *treeNode) partialRefresh() {
  789. if r := cache.Renderer(n.super()); r != nil {
  790. r.(*treeNodeRenderer).partialRefresh()
  791. }
  792. }
  793. func (n *treeNode) update(uid string, depth int) {
  794. n.uid = uid
  795. n.depth = depth
  796. n.propertyLock.Lock()
  797. n.Hidden = false
  798. n.propertyLock.Unlock()
  799. n.partialRefresh()
  800. }
  801. var _ fyne.WidgetRenderer = (*treeNodeRenderer)(nil)
  802. type treeNodeRenderer struct {
  803. widget.BaseRenderer
  804. treeNode *treeNode
  805. background *canvas.Rectangle
  806. }
  807. func (r *treeNodeRenderer) Layout(size fyne.Size) {
  808. x := theme.Padding() + r.treeNode.Indent()
  809. y := float32(0)
  810. r.background.Resize(size)
  811. if r.treeNode.icon != nil {
  812. r.treeNode.icon.Move(fyne.NewPos(x, y))
  813. r.treeNode.icon.Resize(fyne.NewSize(theme.IconInlineSize(), size.Height))
  814. }
  815. x += theme.IconInlineSize()
  816. x += theme.Padding()
  817. if r.treeNode.content != nil {
  818. r.treeNode.content.Move(fyne.NewPos(x, y))
  819. r.treeNode.content.Resize(fyne.NewSize(size.Width-x, size.Height))
  820. }
  821. }
  822. func (r *treeNodeRenderer) MinSize() (min fyne.Size) {
  823. if r.treeNode.content != nil {
  824. min = r.treeNode.content.MinSize()
  825. }
  826. min.Width += theme.InnerPadding() + r.treeNode.Indent() + theme.IconInlineSize()
  827. min.Height = fyne.Max(min.Height, theme.IconInlineSize())
  828. return
  829. }
  830. func (r *treeNodeRenderer) Objects() (objects []fyne.CanvasObject) {
  831. objects = append(objects, r.background)
  832. if r.treeNode.content != nil {
  833. objects = append(objects, r.treeNode.content)
  834. }
  835. if r.treeNode.icon != nil {
  836. objects = append(objects, r.treeNode.icon)
  837. }
  838. return
  839. }
  840. func (r *treeNodeRenderer) Refresh() {
  841. if c := r.treeNode.content; c != nil {
  842. if f := r.treeNode.tree.UpdateNode; f != nil {
  843. f(r.treeNode.uid, r.treeNode.isBranch, c)
  844. }
  845. }
  846. r.partialRefresh()
  847. }
  848. func (r *treeNodeRenderer) partialRefresh() {
  849. if r.treeNode.icon != nil {
  850. r.treeNode.icon.Refresh()
  851. }
  852. r.background.CornerRadius = theme.SelectionRadiusSize()
  853. if len(r.treeNode.tree.selected) > 0 && r.treeNode.uid == r.treeNode.tree.selected[0] {
  854. r.background.FillColor = theme.SelectionColor()
  855. r.background.Show()
  856. } else if r.treeNode.hovered || (r.treeNode.tree.focused && r.treeNode.tree.currentFocus == r.treeNode.uid) {
  857. r.background.FillColor = theme.HoverColor()
  858. r.background.Show()
  859. } else {
  860. r.background.Hide()
  861. }
  862. r.background.Refresh()
  863. r.Layout(r.treeNode.size)
  864. canvas.Refresh(r.treeNode.super())
  865. }
  866. var _ fyne.Widget = (*branch)(nil)
  867. type branch struct {
  868. *treeNode
  869. }
  870. func newBranch(tree *Tree, content fyne.CanvasObject) (b *branch) {
  871. b = &branch{
  872. treeNode: &treeNode{
  873. tree: tree,
  874. icon: newBranchIcon(tree),
  875. isBranch: true,
  876. content: content,
  877. },
  878. }
  879. b.ExtendBaseWidget(b)
  880. return
  881. }
  882. func (b *branch) update(uid string, depth int) {
  883. b.treeNode.update(uid, depth)
  884. b.icon.(*branchIcon).update(uid, depth)
  885. }
  886. var _ fyne.Tappable = (*branchIcon)(nil)
  887. type branchIcon struct {
  888. Icon
  889. tree *Tree
  890. uid string
  891. }
  892. func newBranchIcon(tree *Tree) (i *branchIcon) {
  893. i = &branchIcon{
  894. tree: tree,
  895. }
  896. i.ExtendBaseWidget(i)
  897. return
  898. }
  899. func (i *branchIcon) Refresh() {
  900. if i.tree.IsBranchOpen(i.uid) {
  901. i.Resource = theme.MoveDownIcon()
  902. } else {
  903. i.Resource = theme.NavigateNextIcon()
  904. }
  905. i.Icon.Refresh()
  906. }
  907. func (i *branchIcon) Tapped(*fyne.PointEvent) {
  908. i.tree.ToggleBranch(i.uid)
  909. }
  910. func (i *branchIcon) update(uid string, depth int) {
  911. i.uid = uid
  912. i.Refresh()
  913. }
  914. var _ fyne.Widget = (*leaf)(nil)
  915. type leaf struct {
  916. *treeNode
  917. }
  918. func newLeaf(tree *Tree, content fyne.CanvasObject) (l *leaf) {
  919. l = &leaf{
  920. &treeNode{
  921. tree: tree,
  922. content: content,
  923. isBranch: false,
  924. },
  925. }
  926. l.ExtendBaseWidget(l)
  927. return
  928. }