tree.go 24 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  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. t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
  311. if id == t.currentFocus && p != "" {
  312. t.currentFocus = p
  313. }
  314. })
  315. t.RefreshItem(t.currentFocus)
  316. t.ScrollTo(t.currentFocus)
  317. t.RefreshItem(t.currentFocus)
  318. case fyne.KeyRight:
  319. if t.IsBranch(t.currentFocus) {
  320. t.OpenBranch(t.currentFocus)
  321. }
  322. children := []TreeNodeID{}
  323. if childUIDs := t.ChildUIDs; childUIDs != nil {
  324. children = childUIDs(t.currentFocus)
  325. }
  326. if len(children) > 0 {
  327. t.currentFocus = children[0]
  328. }
  329. t.RefreshItem(t.currentFocus)
  330. t.ScrollTo(t.currentFocus)
  331. t.RefreshItem(t.currentFocus)
  332. case fyne.KeyUp:
  333. t.RefreshItem(t.currentFocus)
  334. previous := ""
  335. t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
  336. if id == t.currentFocus && previous != "" {
  337. t.currentFocus = previous
  338. }
  339. previous = id
  340. })
  341. t.ScrollTo(t.currentFocus)
  342. t.RefreshItem(t.currentFocus)
  343. }
  344. }
  345. // TypedRune is called if a text event happens while this Tree is focused.
  346. //
  347. // Implements: fyne.Focusable
  348. func (t *Tree) TypedRune(_ rune) {
  349. // intentionally left blank
  350. }
  351. // Unselect marks the specified node to be not selected.
  352. func (t *Tree) Unselect(uid TreeNodeID) {
  353. if len(t.selected) == 0 || t.selected[0] != uid {
  354. return
  355. }
  356. t.selected = nil
  357. t.Refresh()
  358. if f := t.OnUnselected; f != nil {
  359. f(uid)
  360. }
  361. }
  362. // UnselectAll sets all nodes to be not selected.
  363. //
  364. // Since: 2.1
  365. func (t *Tree) UnselectAll() {
  366. if len(t.selected) == 0 {
  367. return
  368. }
  369. selected := t.selected
  370. t.selected = nil
  371. t.Refresh()
  372. if f := t.OnUnselected; f != nil {
  373. for _, uid := range selected {
  374. f(uid)
  375. }
  376. }
  377. }
  378. func (t *Tree) ensureOpenMap() {
  379. t.propertyLock.Lock()
  380. defer t.propertyLock.Unlock()
  381. if t.open == nil {
  382. t.open = make(map[string]bool)
  383. }
  384. }
  385. func (t *Tree) findBottom() (y float32, size fyne.Size) {
  386. sep := theme.Padding()
  387. t.walkAll(func(id, _ TreeNodeID, branch bool, _ int) {
  388. size = t.leafMinSize
  389. if branch {
  390. size = t.branchMinSize
  391. }
  392. // Root node is not rendered unless it has been customized
  393. if t.Root == "" && id == "" {
  394. // This is root node, skip
  395. return
  396. }
  397. // If this is not the first item, add a separator
  398. if y > 0 {
  399. y += sep
  400. }
  401. y += size.Height
  402. })
  403. if y > 0 {
  404. y -= sep
  405. }
  406. return
  407. }
  408. func (t *Tree) offsetAndSize(uid TreeNodeID) (y float32, size fyne.Size, found bool) {
  409. t.walkAll(func(id, _ TreeNodeID, branch bool, _ int) {
  410. m := t.leafMinSize
  411. if branch {
  412. m = t.branchMinSize
  413. }
  414. if id == uid {
  415. found = true
  416. size = m
  417. } else if !found {
  418. // Root node is not rendered unless it has been customized
  419. if t.Root == "" && id == "" {
  420. // This is root node, skip
  421. return
  422. }
  423. // If this is not the first item, add a separator
  424. if y > 0 {
  425. y += theme.Padding()
  426. }
  427. y += m.Height
  428. }
  429. })
  430. return
  431. }
  432. func (t *Tree) offsetUpdated(pos fyne.Position) {
  433. if t.offset == pos {
  434. return
  435. }
  436. t.offset = pos
  437. t.scroller.Content.Refresh()
  438. }
  439. func (t *Tree) walk(uid, parent TreeNodeID, depth int, onNode func(TreeNodeID, TreeNodeID, bool, int)) {
  440. if isBranch := t.IsBranch; isBranch != nil {
  441. if isBranch(uid) {
  442. onNode(uid, parent, true, depth)
  443. if t.IsBranchOpen(uid) {
  444. if childUIDs := t.ChildUIDs; childUIDs != nil {
  445. for _, c := range childUIDs(uid) {
  446. t.walk(c, uid, depth+1, onNode)
  447. }
  448. }
  449. }
  450. } else {
  451. onNode(uid, parent, false, depth)
  452. }
  453. }
  454. }
  455. // walkAll visits every open node of the tree and calls the given callback with TreeNodeID, whether node is branch, and the depth of node.
  456. func (t *Tree) walkAll(onNode func(TreeNodeID, TreeNodeID, bool, int)) {
  457. t.walk(t.Root, "", 0, onNode)
  458. }
  459. var _ fyne.WidgetRenderer = (*treeRenderer)(nil)
  460. type treeRenderer struct {
  461. widget.BaseRenderer
  462. tree *Tree
  463. content *treeContent
  464. scroller *widget.Scroll
  465. }
  466. func (r *treeRenderer) MinSize() (min fyne.Size) {
  467. min = r.scroller.MinSize()
  468. min = min.Max(r.tree.branchMinSize)
  469. min = min.Max(r.tree.leafMinSize)
  470. return
  471. }
  472. func (r *treeRenderer) Layout(size fyne.Size) {
  473. r.content.viewport = size
  474. r.scroller.Resize(size)
  475. }
  476. func (r *treeRenderer) Refresh() {
  477. r.updateMinSizes()
  478. s := r.tree.Size()
  479. if s.IsZero() {
  480. r.tree.Resize(r.tree.MinSize())
  481. } else {
  482. r.Layout(s)
  483. }
  484. r.scroller.Refresh()
  485. r.content.Refresh()
  486. canvas.Refresh(r.tree.super())
  487. }
  488. func (r *treeRenderer) updateMinSizes() {
  489. if f := r.tree.CreateNode; f != nil {
  490. r.tree.branchMinSize = newBranch(r.tree, f(true)).MinSize()
  491. r.tree.leafMinSize = newLeaf(r.tree, f(false)).MinSize()
  492. }
  493. }
  494. var _ fyne.Widget = (*treeContent)(nil)
  495. type treeContent struct {
  496. BaseWidget
  497. tree *Tree
  498. viewport fyne.Size
  499. }
  500. func newTreeContent(tree *Tree) (c *treeContent) {
  501. c = &treeContent{
  502. tree: tree,
  503. }
  504. c.ExtendBaseWidget(c)
  505. return
  506. }
  507. func (c *treeContent) CreateRenderer() fyne.WidgetRenderer {
  508. return &treeContentRenderer{
  509. BaseRenderer: widget.BaseRenderer{},
  510. treeContent: c,
  511. branches: make(map[string]*branch),
  512. leaves: make(map[string]*leaf),
  513. branchPool: &syncPool{},
  514. leafPool: &syncPool{},
  515. }
  516. }
  517. func (c *treeContent) Resize(size fyne.Size) {
  518. c.propertyLock.RLock()
  519. s := c.size
  520. c.propertyLock.RUnlock()
  521. if s == size {
  522. return
  523. }
  524. c.propertyLock.Lock()
  525. c.size = size
  526. c.propertyLock.Unlock()
  527. c.Refresh() // trigger a redraw
  528. }
  529. var _ fyne.WidgetRenderer = (*treeContentRenderer)(nil)
  530. type treeContentRenderer struct {
  531. widget.BaseRenderer
  532. treeContent *treeContent
  533. separators []fyne.CanvasObject
  534. objects []fyne.CanvasObject
  535. branches map[string]*branch
  536. leaves map[string]*leaf
  537. branchPool pool
  538. leafPool pool
  539. }
  540. func (r *treeContentRenderer) Layout(size fyne.Size) {
  541. r.treeContent.propertyLock.Lock()
  542. defer r.treeContent.propertyLock.Unlock()
  543. r.objects = nil
  544. branches := make(map[string]*branch)
  545. leaves := make(map[string]*leaf)
  546. pad := theme.Padding()
  547. offsetY := r.treeContent.tree.offset.Y
  548. viewport := r.treeContent.viewport
  549. width := fyne.Max(size.Width, viewport.Width)
  550. separatorCount := 0
  551. separatorThickness := theme.SeparatorThicknessSize()
  552. separatorSize := fyne.NewSize(width, separatorThickness)
  553. separatorOff := (pad + separatorThickness) / 2
  554. y := float32(0)
  555. // walkAll open branches and obtain nodes to render in scroller's viewport
  556. r.treeContent.tree.walkAll(func(uid, _ string, isBranch bool, depth int) {
  557. // Root node is not rendered unless it has been customized
  558. if r.treeContent.tree.Root == "" {
  559. depth = depth - 1
  560. if uid == "" {
  561. // This is root node, skip
  562. return
  563. }
  564. }
  565. // If this is not the first item, add a separator
  566. addSeparator := y > 0
  567. if addSeparator {
  568. y += pad
  569. separatorCount++
  570. }
  571. m := r.treeContent.tree.leafMinSize
  572. if isBranch {
  573. m = r.treeContent.tree.branchMinSize
  574. }
  575. if y+m.Height < offsetY {
  576. // Node is above viewport and not visible
  577. } else if y > offsetY+viewport.Height {
  578. // Node is below viewport and not visible
  579. } else {
  580. // Node is in viewport
  581. if addSeparator {
  582. var separator fyne.CanvasObject
  583. if separatorCount < len(r.separators) {
  584. separator = r.separators[separatorCount]
  585. } else {
  586. separator = NewSeparator()
  587. r.separators = append(r.separators, separator)
  588. }
  589. separator.Move(fyne.NewPos(0, y-separatorOff))
  590. separator.Resize(separatorSize)
  591. r.objects = append(r.objects, separator)
  592. separatorCount++
  593. }
  594. var n fyne.CanvasObject
  595. if isBranch {
  596. b, ok := r.branches[uid]
  597. if !ok {
  598. b = r.getBranch()
  599. if f := r.treeContent.tree.UpdateNode; f != nil {
  600. f(uid, true, b.Content())
  601. }
  602. b.update(uid, depth)
  603. }
  604. branches[uid] = b
  605. n = b
  606. r.objects = append(r.objects, b)
  607. } else {
  608. l, ok := r.leaves[uid]
  609. if !ok {
  610. l = r.getLeaf()
  611. if f := r.treeContent.tree.UpdateNode; f != nil {
  612. f(uid, false, l.Content())
  613. }
  614. l.update(uid, depth)
  615. }
  616. leaves[uid] = l
  617. n = l
  618. r.objects = append(r.objects, l)
  619. }
  620. if n != nil {
  621. n.Move(fyne.NewPos(0, y))
  622. n.Resize(fyne.NewSize(width, m.Height))
  623. }
  624. }
  625. y += m.Height
  626. })
  627. // Hide any separators that haven't been reused
  628. for ; separatorCount < len(r.separators); separatorCount++ {
  629. r.separators[separatorCount].Hide()
  630. }
  631. // Release any nodes that haven't been reused
  632. for uid, b := range r.branches {
  633. if _, ok := branches[uid]; !ok {
  634. r.branchPool.Release(b)
  635. }
  636. }
  637. for uid, l := range r.leaves {
  638. if _, ok := leaves[uid]; !ok {
  639. r.leafPool.Release(l)
  640. }
  641. }
  642. r.branches = branches
  643. r.leaves = leaves
  644. }
  645. func (r *treeContentRenderer) MinSize() (min fyne.Size) {
  646. r.treeContent.propertyLock.Lock()
  647. defer r.treeContent.propertyLock.Unlock()
  648. r.treeContent.tree.walkAll(func(uid, _ string, isBranch bool, depth int) {
  649. // Root node is not rendered unless it has been customized
  650. if r.treeContent.tree.Root == "" {
  651. depth = depth - 1
  652. if uid == "" {
  653. // This is root node, skip
  654. return
  655. }
  656. }
  657. // If this is not the first item, add a separator
  658. if min.Height > 0 {
  659. min.Height += theme.Padding()
  660. }
  661. m := r.treeContent.tree.leafMinSize
  662. if isBranch {
  663. m = r.treeContent.tree.branchMinSize
  664. }
  665. m.Width += float32(depth) * (theme.IconInlineSize() + theme.Padding())
  666. min.Width = fyne.Max(min.Width, m.Width)
  667. min.Height += m.Height
  668. })
  669. return
  670. }
  671. func (r *treeContentRenderer) Objects() []fyne.CanvasObject {
  672. return r.objects
  673. }
  674. func (r *treeContentRenderer) Refresh() {
  675. r.refreshForID(allTreeNodesID)
  676. }
  677. func (r *treeContentRenderer) refreshForID(toDraw TreeNodeID) {
  678. s := r.treeContent.Size()
  679. if s.IsZero() {
  680. r.treeContent.Resize(r.treeContent.MinSize().Max(r.treeContent.tree.Size()))
  681. } else {
  682. r.Layout(s)
  683. }
  684. r.treeContent.propertyLock.RLock()
  685. for id, b := range r.branches {
  686. if toDraw != allTreeNodesID && id != toDraw {
  687. continue
  688. }
  689. b.Refresh()
  690. }
  691. for id, l := range r.leaves {
  692. if toDraw != allTreeNodesID && id != toDraw {
  693. continue
  694. }
  695. l.Refresh()
  696. }
  697. r.treeContent.propertyLock.RUnlock()
  698. canvas.Refresh(r.treeContent.super())
  699. }
  700. func (r *treeContentRenderer) getBranch() (b *branch) {
  701. o := r.branchPool.Obtain()
  702. if o != nil {
  703. b = o.(*branch)
  704. } else {
  705. var content fyne.CanvasObject
  706. if f := r.treeContent.tree.CreateNode; f != nil {
  707. content = f(true)
  708. }
  709. b = newBranch(r.treeContent.tree, content)
  710. }
  711. return
  712. }
  713. func (r *treeContentRenderer) getLeaf() (l *leaf) {
  714. o := r.leafPool.Obtain()
  715. if o != nil {
  716. l = o.(*leaf)
  717. } else {
  718. var content fyne.CanvasObject
  719. if f := r.treeContent.tree.CreateNode; f != nil {
  720. content = f(false)
  721. }
  722. l = newLeaf(r.treeContent.tree, content)
  723. }
  724. return
  725. }
  726. var _ desktop.Hoverable = (*treeNode)(nil)
  727. var _ fyne.CanvasObject = (*treeNode)(nil)
  728. var _ fyne.Tappable = (*treeNode)(nil)
  729. type treeNode struct {
  730. BaseWidget
  731. tree *Tree
  732. uid string
  733. depth int
  734. hovered bool
  735. icon fyne.CanvasObject
  736. isBranch bool
  737. content fyne.CanvasObject
  738. }
  739. func (n *treeNode) Content() fyne.CanvasObject {
  740. return n.content
  741. }
  742. func (n *treeNode) CreateRenderer() fyne.WidgetRenderer {
  743. background := canvas.NewRectangle(theme.HoverColor())
  744. background.CornerRadius = theme.SelectionRadiusSize()
  745. background.Hide()
  746. return &treeNodeRenderer{
  747. BaseRenderer: widget.BaseRenderer{},
  748. treeNode: n,
  749. background: background,
  750. }
  751. }
  752. func (n *treeNode) Indent() float32 {
  753. return float32(n.depth) * (theme.IconInlineSize() + theme.Padding())
  754. }
  755. // MouseIn is called when a desktop pointer enters the widget
  756. func (n *treeNode) MouseIn(*desktop.MouseEvent) {
  757. n.hovered = true
  758. n.partialRefresh()
  759. }
  760. // MouseMoved is called when a desktop pointer hovers over the widget
  761. func (n *treeNode) MouseMoved(*desktop.MouseEvent) {
  762. }
  763. // MouseOut is called when a desktop pointer exits the widget
  764. func (n *treeNode) MouseOut() {
  765. n.hovered = false
  766. n.partialRefresh()
  767. }
  768. func (n *treeNode) Tapped(*fyne.PointEvent) {
  769. if n.tree.currentFocus != "" {
  770. n.tree.RefreshItem(n.tree.currentFocus)
  771. }
  772. n.tree.Select(n.uid)
  773. canvas := fyne.CurrentApp().Driver().CanvasForObject(n.tree)
  774. if canvas != nil {
  775. canvas.Focus(n.tree)
  776. }
  777. n.tree.currentFocus = n.uid
  778. n.Refresh()
  779. }
  780. func (n *treeNode) partialRefresh() {
  781. if r := cache.Renderer(n.super()); r != nil {
  782. r.(*treeNodeRenderer).partialRefresh()
  783. }
  784. }
  785. func (n *treeNode) update(uid string, depth int) {
  786. n.uid = uid
  787. n.depth = depth
  788. n.propertyLock.Lock()
  789. n.Hidden = false
  790. n.propertyLock.Unlock()
  791. n.partialRefresh()
  792. }
  793. var _ fyne.WidgetRenderer = (*treeNodeRenderer)(nil)
  794. type treeNodeRenderer struct {
  795. widget.BaseRenderer
  796. treeNode *treeNode
  797. background *canvas.Rectangle
  798. }
  799. func (r *treeNodeRenderer) Layout(size fyne.Size) {
  800. x := theme.Padding() + r.treeNode.Indent()
  801. y := float32(0)
  802. r.background.Resize(size)
  803. if r.treeNode.icon != nil {
  804. r.treeNode.icon.Move(fyne.NewPos(x, y))
  805. r.treeNode.icon.Resize(fyne.NewSize(theme.IconInlineSize(), size.Height))
  806. }
  807. x += theme.IconInlineSize()
  808. x += theme.Padding()
  809. if r.treeNode.content != nil {
  810. r.treeNode.content.Move(fyne.NewPos(x, y))
  811. r.treeNode.content.Resize(fyne.NewSize(size.Width-x, size.Height))
  812. }
  813. }
  814. func (r *treeNodeRenderer) MinSize() (min fyne.Size) {
  815. if r.treeNode.content != nil {
  816. min = r.treeNode.content.MinSize()
  817. }
  818. min.Width += theme.InnerPadding() + r.treeNode.Indent() + theme.IconInlineSize()
  819. min.Height = fyne.Max(min.Height, theme.IconInlineSize())
  820. return
  821. }
  822. func (r *treeNodeRenderer) Objects() (objects []fyne.CanvasObject) {
  823. objects = append(objects, r.background)
  824. if r.treeNode.content != nil {
  825. objects = append(objects, r.treeNode.content)
  826. }
  827. if r.treeNode.icon != nil {
  828. objects = append(objects, r.treeNode.icon)
  829. }
  830. return
  831. }
  832. func (r *treeNodeRenderer) Refresh() {
  833. if c := r.treeNode.content; c != nil {
  834. if f := r.treeNode.tree.UpdateNode; f != nil {
  835. f(r.treeNode.uid, r.treeNode.isBranch, c)
  836. }
  837. }
  838. r.partialRefresh()
  839. }
  840. func (r *treeNodeRenderer) partialRefresh() {
  841. if r.treeNode.icon != nil {
  842. r.treeNode.icon.Refresh()
  843. }
  844. r.background.CornerRadius = theme.SelectionRadiusSize()
  845. if len(r.treeNode.tree.selected) > 0 && r.treeNode.uid == r.treeNode.tree.selected[0] {
  846. r.background.FillColor = theme.SelectionColor()
  847. r.background.Show()
  848. } else if r.treeNode.hovered || (r.treeNode.tree.focused && r.treeNode.tree.currentFocus == r.treeNode.uid) {
  849. r.background.FillColor = theme.HoverColor()
  850. r.background.Show()
  851. } else {
  852. r.background.Hide()
  853. }
  854. r.background.Refresh()
  855. r.Layout(r.treeNode.size)
  856. canvas.Refresh(r.treeNode.super())
  857. }
  858. var _ fyne.Widget = (*branch)(nil)
  859. type branch struct {
  860. *treeNode
  861. }
  862. func newBranch(tree *Tree, content fyne.CanvasObject) (b *branch) {
  863. b = &branch{
  864. treeNode: &treeNode{
  865. tree: tree,
  866. icon: newBranchIcon(tree),
  867. isBranch: true,
  868. content: content,
  869. },
  870. }
  871. b.ExtendBaseWidget(b)
  872. return
  873. }
  874. func (b *branch) update(uid string, depth int) {
  875. b.treeNode.update(uid, depth)
  876. b.icon.(*branchIcon).update(uid, depth)
  877. }
  878. var _ fyne.Tappable = (*branchIcon)(nil)
  879. type branchIcon struct {
  880. Icon
  881. tree *Tree
  882. uid string
  883. }
  884. func newBranchIcon(tree *Tree) (i *branchIcon) {
  885. i = &branchIcon{
  886. tree: tree,
  887. }
  888. i.ExtendBaseWidget(i)
  889. return
  890. }
  891. func (i *branchIcon) Refresh() {
  892. if i.tree.IsBranchOpen(i.uid) {
  893. i.Resource = theme.MoveDownIcon()
  894. } else {
  895. i.Resource = theme.NavigateNextIcon()
  896. }
  897. i.Icon.Refresh()
  898. }
  899. func (i *branchIcon) Tapped(*fyne.PointEvent) {
  900. i.tree.ToggleBranch(i.uid)
  901. }
  902. func (i *branchIcon) update(uid string, depth int) {
  903. i.uid = uid
  904. i.Refresh()
  905. }
  906. var _ fyne.Widget = (*leaf)(nil)
  907. type leaf struct {
  908. *treeNode
  909. }
  910. func newLeaf(tree *Tree, content fyne.CanvasObject) (l *leaf) {
  911. l = &leaf{
  912. &treeNode{
  913. tree: tree,
  914. content: content,
  915. isBranch: false,
  916. },
  917. }
  918. l.ExtendBaseWidget(l)
  919. return
  920. }