tree.go 20 KB

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