list.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. package widget
  2. import (
  3. "fmt"
  4. "math"
  5. "sort"
  6. "sync"
  7. "fyne.io/fyne/v2"
  8. "fyne.io/fyne/v2/canvas"
  9. "fyne.io/fyne/v2/data/binding"
  10. "fyne.io/fyne/v2/driver/desktop"
  11. "fyne.io/fyne/v2/internal/widget"
  12. "fyne.io/fyne/v2/theme"
  13. )
  14. // ListItemID uniquely identifies an item within a list.
  15. type ListItemID = int
  16. // Declare conformity with interfaces.
  17. var _ fyne.Widget = (*List)(nil)
  18. var _ fyne.Focusable = (*List)(nil)
  19. // List is a widget that pools list items for performance and
  20. // lays the items out in a vertical direction inside of a scroller.
  21. // List requires that all items are the same size.
  22. //
  23. // Since: 1.4
  24. type List struct {
  25. BaseWidget
  26. Length func() int `json:"-"`
  27. CreateItem func() fyne.CanvasObject `json:"-"`
  28. UpdateItem func(id ListItemID, item fyne.CanvasObject) `json:"-"`
  29. OnSelected func(id ListItemID) `json:"-"`
  30. OnUnselected func(id ListItemID) `json:"-"`
  31. currentFocus ListItemID
  32. focused bool
  33. scroller *widget.Scroll
  34. selected []ListItemID
  35. itemMin fyne.Size
  36. itemHeights map[ListItemID]float32
  37. offsetY float32
  38. offsetUpdated func(fyne.Position)
  39. }
  40. // NewList creates and returns a list widget for displaying items in
  41. // a vertical layout with scrolling and caching for performance.
  42. //
  43. // Since: 1.4
  44. func NewList(length func() int, createItem func() fyne.CanvasObject, updateItem func(ListItemID, fyne.CanvasObject)) *List {
  45. list := &List{Length: length, CreateItem: createItem, UpdateItem: updateItem}
  46. list.ExtendBaseWidget(list)
  47. return list
  48. }
  49. // NewListWithData creates a new list widget that will display the contents of the provided data.
  50. //
  51. // Since: 2.0
  52. func NewListWithData(data binding.DataList, createItem func() fyne.CanvasObject, updateItem func(binding.DataItem, fyne.CanvasObject)) *List {
  53. l := NewList(
  54. data.Length,
  55. createItem,
  56. func(i ListItemID, o fyne.CanvasObject) {
  57. item, err := data.GetItem(i)
  58. if err != nil {
  59. fyne.LogError(fmt.Sprintf("Error getting data item %d", i), err)
  60. return
  61. }
  62. updateItem(item, o)
  63. })
  64. data.AddListener(binding.NewDataListener(l.Refresh))
  65. return l
  66. }
  67. // CreateRenderer is a private method to Fyne which links this widget to its renderer.
  68. func (l *List) CreateRenderer() fyne.WidgetRenderer {
  69. l.ExtendBaseWidget(l)
  70. if f := l.CreateItem; f != nil && l.itemMin.IsZero() {
  71. l.itemMin = f().MinSize()
  72. }
  73. layout := &fyne.Container{Layout: newListLayout(l)}
  74. l.scroller = widget.NewVScroll(layout)
  75. layout.Resize(layout.MinSize())
  76. objects := []fyne.CanvasObject{l.scroller}
  77. return newListRenderer(objects, l, l.scroller, layout)
  78. }
  79. // FocusGained is called after this List has gained focus.
  80. //
  81. // Implements: fyne.Focusable
  82. func (l *List) FocusGained() {
  83. l.focused = true
  84. l.scrollTo(l.currentFocus)
  85. l.RefreshItem(l.currentFocus)
  86. }
  87. // FocusLost is called after this List has lost focus.
  88. //
  89. // Implements: fyne.Focusable
  90. func (l *List) FocusLost() {
  91. l.focused = false
  92. l.RefreshItem(l.currentFocus)
  93. }
  94. // MinSize returns the size that this widget should not shrink below.
  95. func (l *List) MinSize() fyne.Size {
  96. l.ExtendBaseWidget(l)
  97. return l.BaseWidget.MinSize()
  98. }
  99. // RefreshItem refreshes a single item, specified by the item ID passed in.
  100. //
  101. // Since: 2.4
  102. func (l *List) RefreshItem(id ListItemID) {
  103. if l.scroller == nil {
  104. return
  105. }
  106. l.BaseWidget.Refresh()
  107. lo := l.scroller.Content.(*fyne.Container).Layout.(*listLayout)
  108. lo.renderLock.RLock() // ensures we are not changing visible info in render code during the search
  109. item, ok := lo.searchVisible(lo.visible, id)
  110. lo.renderLock.RUnlock()
  111. if ok {
  112. lo.setupListItem(item, id, l.focused && l.currentFocus == id)
  113. }
  114. }
  115. // SetItemHeight supports changing the height of the specified list item. Items normally take the height of the template
  116. // returned from the CreateItem callback. The height parameter uses the same units as a fyne.Size type and refers
  117. // to the internal content height not including the divider size.
  118. //
  119. // Since: 2.3
  120. func (l *List) SetItemHeight(id ListItemID, height float32) {
  121. l.propertyLock.Lock()
  122. if l.itemHeights == nil {
  123. l.itemHeights = make(map[ListItemID]float32)
  124. }
  125. refresh := l.itemHeights[id] != height
  126. l.itemHeights[id] = height
  127. l.propertyLock.Unlock()
  128. if refresh {
  129. l.RefreshItem(id)
  130. }
  131. }
  132. func (l *List) scrollTo(id ListItemID) {
  133. if l.scroller == nil {
  134. return
  135. }
  136. separatorThickness := theme.Padding()
  137. y := float32(0)
  138. lastItemHeight := l.itemMin.Height
  139. if l.itemHeights == nil || len(l.itemHeights) == 0 {
  140. y = (float32(id) * l.itemMin.Height) + (float32(id) * separatorThickness)
  141. } else {
  142. for i := 0; i < id; i++ {
  143. height := l.itemMin.Height
  144. if h, ok := l.itemHeights[i]; ok {
  145. height = h
  146. }
  147. y += height + separatorThickness
  148. lastItemHeight = height
  149. }
  150. }
  151. if y < l.scroller.Offset.Y {
  152. l.scroller.Offset.Y = y
  153. } else if y+l.itemMin.Height > l.scroller.Offset.Y+l.scroller.Size().Height {
  154. l.scroller.Offset.Y = y + lastItemHeight - l.scroller.Size().Height
  155. }
  156. l.offsetUpdated(l.scroller.Offset)
  157. }
  158. // Resize is called when this list should change size. We refresh to ensure invisible items are drawn.
  159. func (l *List) Resize(s fyne.Size) {
  160. l.BaseWidget.Resize(s)
  161. if l.scroller == nil {
  162. return
  163. }
  164. l.offsetUpdated(l.scroller.Offset)
  165. l.scroller.Content.(*fyne.Container).Layout.(*listLayout).updateList(false)
  166. }
  167. // Select add the item identified by the given ID to the selection.
  168. func (l *List) Select(id ListItemID) {
  169. if len(l.selected) > 0 && id == l.selected[0] {
  170. return
  171. }
  172. length := 0
  173. if f := l.Length; f != nil {
  174. length = f()
  175. }
  176. if id < 0 || id >= length {
  177. return
  178. }
  179. old := l.selected
  180. l.selected = []ListItemID{id}
  181. defer func() {
  182. if f := l.OnUnselected; f != nil && len(old) > 0 {
  183. f(old[0])
  184. }
  185. if f := l.OnSelected; f != nil {
  186. f(id)
  187. }
  188. }()
  189. l.scrollTo(id)
  190. l.Refresh()
  191. }
  192. // ScrollTo scrolls to the item represented by id
  193. //
  194. // Since: 2.1
  195. func (l *List) ScrollTo(id ListItemID) {
  196. length := 0
  197. if f := l.Length; f != nil {
  198. length = f()
  199. }
  200. if id < 0 || id >= length {
  201. return
  202. }
  203. l.scrollTo(id)
  204. l.Refresh()
  205. }
  206. // ScrollToBottom scrolls to the end of the list
  207. //
  208. // Since: 2.1
  209. func (l *List) ScrollToBottom() {
  210. length := 0
  211. if f := l.Length; f != nil {
  212. length = f()
  213. }
  214. if length > 0 {
  215. length--
  216. }
  217. l.scrollTo(length)
  218. l.Refresh()
  219. }
  220. // ScrollToTop scrolls to the start of the list
  221. //
  222. // Since: 2.1
  223. func (l *List) ScrollToTop() {
  224. l.scrollTo(0)
  225. l.Refresh()
  226. }
  227. // TypedKey is called if a key event happens while this List is focused.
  228. //
  229. // Implements: fyne.Focusable
  230. func (l *List) TypedKey(event *fyne.KeyEvent) {
  231. switch event.Name {
  232. case fyne.KeySpace:
  233. l.Select(l.currentFocus)
  234. case fyne.KeyDown:
  235. if f := l.Length; f != nil && l.currentFocus >= f()-1 {
  236. return
  237. }
  238. l.RefreshItem(l.currentFocus)
  239. l.currentFocus++
  240. l.scrollTo(l.currentFocus)
  241. l.RefreshItem(l.currentFocus)
  242. case fyne.KeyUp:
  243. if l.currentFocus <= 0 {
  244. return
  245. }
  246. l.RefreshItem(l.currentFocus)
  247. l.currentFocus--
  248. l.scrollTo(l.currentFocus)
  249. l.RefreshItem(l.currentFocus)
  250. }
  251. }
  252. // TypedRune is called if a text event happens while this List is focused.
  253. //
  254. // Implements: fyne.Focusable
  255. func (l *List) TypedRune(_ rune) {
  256. // intentionally left blank
  257. }
  258. // Unselect removes the item identified by the given ID from the selection.
  259. func (l *List) Unselect(id ListItemID) {
  260. if len(l.selected) == 0 || l.selected[0] != id {
  261. return
  262. }
  263. l.selected = nil
  264. l.Refresh()
  265. if f := l.OnUnselected; f != nil {
  266. f(id)
  267. }
  268. }
  269. // UnselectAll removes all items from the selection.
  270. //
  271. // Since: 2.1
  272. func (l *List) UnselectAll() {
  273. if len(l.selected) == 0 {
  274. return
  275. }
  276. selected := l.selected
  277. l.selected = nil
  278. l.Refresh()
  279. if f := l.OnUnselected; f != nil {
  280. for _, id := range selected {
  281. f(id)
  282. }
  283. }
  284. }
  285. // fills l.visibleRowHeights and also returns offY and minRow
  286. func (l *listLayout) calculateVisibleRowHeights(itemHeight float32, length int) (offY float32, minRow int) {
  287. rowOffset := float32(0)
  288. isVisible := false
  289. l.visibleRowHeights = l.visibleRowHeights[:0]
  290. if l.list.scroller.Size().Height <= 0 {
  291. return
  292. }
  293. // theme.Padding is a slow call, so we cache it
  294. padding := theme.Padding()
  295. if len(l.list.itemHeights) == 0 {
  296. paddedItemHeight := itemHeight + padding
  297. offY = float32(math.Floor(float64(l.list.offsetY/paddedItemHeight))) * paddedItemHeight
  298. minRow = int(math.Floor(float64(offY / paddedItemHeight)))
  299. maxRow := int(math.Ceil(float64((offY + l.list.scroller.Size().Height) / paddedItemHeight)))
  300. if minRow > length-1 {
  301. minRow = length - 1
  302. }
  303. if minRow < 0 {
  304. minRow = 0
  305. offY = 0
  306. }
  307. if maxRow > length {
  308. maxRow = length
  309. }
  310. for i := 0; i < maxRow-minRow; i++ {
  311. l.visibleRowHeights = append(l.visibleRowHeights, itemHeight)
  312. }
  313. return
  314. }
  315. for i := 0; i < length; i++ {
  316. height := itemHeight
  317. if h, ok := l.list.itemHeights[i]; ok {
  318. height = h
  319. }
  320. if rowOffset <= l.list.offsetY-height-padding {
  321. // before scroll
  322. } else if rowOffset <= l.list.offsetY {
  323. minRow = i
  324. offY = rowOffset
  325. isVisible = true
  326. }
  327. if rowOffset >= l.list.offsetY+l.list.scroller.Size().Height {
  328. break
  329. }
  330. rowOffset += height + padding
  331. if isVisible {
  332. l.visibleRowHeights = append(l.visibleRowHeights, height)
  333. }
  334. }
  335. return
  336. }
  337. // Declare conformity with WidgetRenderer interface.
  338. var _ fyne.WidgetRenderer = (*listRenderer)(nil)
  339. type listRenderer struct {
  340. widget.BaseRenderer
  341. list *List
  342. scroller *widget.Scroll
  343. layout *fyne.Container
  344. }
  345. func newListRenderer(objects []fyne.CanvasObject, l *List, scroller *widget.Scroll, layout *fyne.Container) *listRenderer {
  346. lr := &listRenderer{BaseRenderer: widget.NewBaseRenderer(objects), list: l, scroller: scroller, layout: layout}
  347. lr.scroller.OnScrolled = l.offsetUpdated
  348. return lr
  349. }
  350. func (l *listRenderer) Layout(size fyne.Size) {
  351. l.scroller.Resize(size)
  352. }
  353. func (l *listRenderer) MinSize() fyne.Size {
  354. return l.scroller.MinSize().Max(l.list.itemMin)
  355. }
  356. func (l *listRenderer) Refresh() {
  357. if f := l.list.CreateItem; f != nil {
  358. l.list.itemMin = f().MinSize()
  359. }
  360. l.Layout(l.list.Size())
  361. l.scroller.Refresh()
  362. l.layout.Layout.(*listLayout).updateList(false)
  363. canvas.Refresh(l.list.super())
  364. }
  365. // Declare conformity with interfaces.
  366. var _ fyne.Widget = (*listItem)(nil)
  367. var _ fyne.Tappable = (*listItem)(nil)
  368. var _ desktop.Hoverable = (*listItem)(nil)
  369. type listItem struct {
  370. BaseWidget
  371. onTapped func()
  372. background *canvas.Rectangle
  373. child fyne.CanvasObject
  374. hovered, selected bool
  375. }
  376. func newListItem(child fyne.CanvasObject, tapped func()) *listItem {
  377. li := &listItem{
  378. child: child,
  379. onTapped: tapped,
  380. }
  381. li.ExtendBaseWidget(li)
  382. return li
  383. }
  384. // CreateRenderer is a private method to Fyne which links this widget to its renderer.
  385. func (li *listItem) CreateRenderer() fyne.WidgetRenderer {
  386. li.ExtendBaseWidget(li)
  387. li.background = canvas.NewRectangle(theme.HoverColor())
  388. li.background.CornerRadius = theme.SelectionRadiusSize()
  389. li.background.Hide()
  390. objects := []fyne.CanvasObject{li.background, li.child}
  391. return &listItemRenderer{widget.NewBaseRenderer(objects), li}
  392. }
  393. // MinSize returns the size that this widget should not shrink below.
  394. func (li *listItem) MinSize() fyne.Size {
  395. li.ExtendBaseWidget(li)
  396. return li.BaseWidget.MinSize()
  397. }
  398. // MouseIn is called when a desktop pointer enters the widget.
  399. func (li *listItem) MouseIn(*desktop.MouseEvent) {
  400. li.hovered = true
  401. li.Refresh()
  402. }
  403. // MouseMoved is called when a desktop pointer hovers over the widget.
  404. func (li *listItem) MouseMoved(*desktop.MouseEvent) {
  405. }
  406. // MouseOut is called when a desktop pointer exits the widget.
  407. func (li *listItem) MouseOut() {
  408. li.hovered = false
  409. li.Refresh()
  410. }
  411. // Tapped is called when a pointer tapped event is captured and triggers any tap handler.
  412. func (li *listItem) Tapped(*fyne.PointEvent) {
  413. if li.onTapped != nil {
  414. li.selected = true
  415. li.Refresh()
  416. li.onTapped()
  417. }
  418. }
  419. // Declare conformity with the WidgetRenderer interface.
  420. var _ fyne.WidgetRenderer = (*listItemRenderer)(nil)
  421. type listItemRenderer struct {
  422. widget.BaseRenderer
  423. item *listItem
  424. }
  425. // MinSize calculates the minimum size of a listItem.
  426. // This is based on the size of the status indicator and the size of the child object.
  427. func (li *listItemRenderer) MinSize() fyne.Size {
  428. return li.item.child.MinSize()
  429. }
  430. // Layout the components of the listItem widget.
  431. func (li *listItemRenderer) Layout(size fyne.Size) {
  432. li.item.background.Resize(size)
  433. li.item.child.Resize(size)
  434. }
  435. func (li *listItemRenderer) Refresh() {
  436. li.item.background.CornerRadius = theme.SelectionRadiusSize()
  437. if li.item.selected {
  438. li.item.background.FillColor = theme.SelectionColor()
  439. li.item.background.Show()
  440. } else if li.item.hovered {
  441. li.item.background.FillColor = theme.HoverColor()
  442. li.item.background.Show()
  443. } else {
  444. li.item.background.Hide()
  445. }
  446. li.item.background.Refresh()
  447. canvas.Refresh(li.item.super())
  448. }
  449. // Declare conformity with Layout interface.
  450. var _ fyne.Layout = (*listLayout)(nil)
  451. type listItemAndID struct {
  452. item *listItem
  453. id ListItemID
  454. }
  455. type listLayout struct {
  456. list *List
  457. separators []fyne.CanvasObject
  458. children []fyne.CanvasObject
  459. itemPool syncPool
  460. visible []listItemAndID
  461. slicePool sync.Pool // *[]itemAndID
  462. visibleRowHeights []float32
  463. renderLock sync.RWMutex
  464. }
  465. func newListLayout(list *List) fyne.Layout {
  466. l := &listLayout{list: list}
  467. l.slicePool.New = func() interface{} {
  468. s := make([]listItemAndID, 0)
  469. return &s
  470. }
  471. list.offsetUpdated = l.offsetUpdated
  472. return l
  473. }
  474. func (l *listLayout) Layout([]fyne.CanvasObject, fyne.Size) {
  475. l.updateList(true)
  476. }
  477. func (l *listLayout) MinSize([]fyne.CanvasObject) fyne.Size {
  478. l.list.propertyLock.Lock()
  479. defer l.list.propertyLock.Unlock()
  480. items := 0
  481. if f := l.list.Length; f == nil {
  482. return fyne.NewSize(0, 0)
  483. } else {
  484. items = f()
  485. }
  486. separatorThickness := theme.Padding()
  487. if l.list.itemHeights == nil || len(l.list.itemHeights) == 0 {
  488. return fyne.NewSize(l.list.itemMin.Width,
  489. (l.list.itemMin.Height+separatorThickness)*float32(items)-separatorThickness)
  490. }
  491. height := float32(0)
  492. templateHeight := l.list.itemMin.Height
  493. for item := 0; item < items; item++ {
  494. itemHeight, ok := l.list.itemHeights[item]
  495. if ok {
  496. height += itemHeight
  497. } else {
  498. height += templateHeight
  499. }
  500. }
  501. return fyne.NewSize(l.list.itemMin.Width, height+separatorThickness*float32(items-1))
  502. }
  503. func (l *listLayout) getItem() *listItem {
  504. item := l.itemPool.Obtain()
  505. if item == nil {
  506. if f := l.list.CreateItem; f != nil {
  507. item = newListItem(f(), nil)
  508. }
  509. }
  510. return item.(*listItem)
  511. }
  512. func (l *listLayout) offsetUpdated(pos fyne.Position) {
  513. if l.list.offsetY == pos.Y {
  514. return
  515. }
  516. l.list.offsetY = pos.Y
  517. l.updateList(true)
  518. }
  519. func (l *listLayout) setupListItem(li *listItem, id ListItemID, focus bool) {
  520. previousIndicator := li.selected
  521. li.selected = false
  522. for _, s := range l.list.selected {
  523. if id == s {
  524. li.selected = true
  525. break
  526. }
  527. }
  528. if focus {
  529. li.hovered = true
  530. li.Refresh()
  531. } else if previousIndicator != li.selected || li.hovered {
  532. li.hovered = false
  533. li.Refresh()
  534. }
  535. if f := l.list.UpdateItem; f != nil {
  536. f(id, li.child)
  537. }
  538. li.onTapped = func() {
  539. if !fyne.CurrentDevice().IsMobile() {
  540. canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list)
  541. if canvas != nil {
  542. canvas.Focus(l.list)
  543. }
  544. l.list.currentFocus = id
  545. }
  546. l.list.Select(id)
  547. }
  548. }
  549. func (l *listLayout) updateList(newOnly bool) {
  550. l.renderLock.Lock()
  551. separatorThickness := theme.Padding()
  552. width := l.list.Size().Width
  553. length := 0
  554. if f := l.list.Length; f != nil {
  555. length = f()
  556. }
  557. if l.list.UpdateItem == nil {
  558. fyne.LogError("Missing UpdateCell callback required for List", nil)
  559. }
  560. // Keep pointer reference for copying slice header when returning to the pool
  561. // https://blog.mike.norgate.xyz/unlocking-go-slice-performance-navigating-sync-pool-for-enhanced-efficiency-7cb63b0b453e
  562. wasVisiblePtr := l.slicePool.Get().(*[]listItemAndID)
  563. wasVisible := (*wasVisiblePtr)[:0]
  564. wasVisible = append(wasVisible, l.visible...)
  565. l.list.propertyLock.Lock()
  566. offY, minRow := l.calculateVisibleRowHeights(l.list.itemMin.Height, length)
  567. l.list.propertyLock.Unlock()
  568. if len(l.visibleRowHeights) == 0 && length > 0 { // we can't show anything until we have some dimensions
  569. l.renderLock.Unlock() // user code should not be locked
  570. return
  571. }
  572. oldVisibleLen := len(l.visible)
  573. l.visible = l.visible[:0]
  574. oldChildrenLen := len(l.children)
  575. l.children = l.children[:0]
  576. y := offY
  577. for index, itemHeight := range l.visibleRowHeights {
  578. row := index + minRow
  579. size := fyne.NewSize(width, itemHeight)
  580. c, ok := l.searchVisible(wasVisible, row)
  581. if !ok {
  582. c = l.getItem()
  583. if c == nil {
  584. continue
  585. }
  586. c.Resize(size)
  587. }
  588. c.Move(fyne.NewPos(0, y))
  589. c.Resize(size)
  590. y += itemHeight + separatorThickness
  591. l.visible = append(l.visible, listItemAndID{id: row, item: c})
  592. l.children = append(l.children, c)
  593. }
  594. l.nilOldSliceData(l.children, len(l.children), oldChildrenLen)
  595. l.nilOldVisibleSliceData(l.visible, len(l.visible), oldVisibleLen)
  596. for _, wasVis := range wasVisible {
  597. if _, ok := l.searchVisible(l.visible, wasVis.id); !ok {
  598. l.itemPool.Release(wasVis.item)
  599. }
  600. }
  601. l.updateSeparators()
  602. c := l.list.scroller.Content.(*fyne.Container)
  603. oldObjLen := len(c.Objects)
  604. c.Objects = c.Objects[:0]
  605. c.Objects = append(c.Objects, l.children...)
  606. c.Objects = append(c.Objects, l.separators...)
  607. l.nilOldSliceData(c.Objects, len(c.Objects), oldObjLen)
  608. // make a local deep copy of l.visible since rest of this function is unlocked
  609. // and cannot safely access l.visible
  610. visiblePtr := l.slicePool.Get().(*[]listItemAndID)
  611. visible := (*visiblePtr)[:0]
  612. visible = append(visible, l.visible...)
  613. l.renderLock.Unlock() // user code should not be locked
  614. if newOnly {
  615. for _, vis := range visible {
  616. if _, ok := l.searchVisible(wasVisible, vis.id); !ok {
  617. l.setupListItem(vis.item, vis.id, l.list.focused && l.list.currentFocus == vis.id)
  618. }
  619. }
  620. } else {
  621. for _, vis := range visible {
  622. l.setupListItem(vis.item, vis.id, l.list.focused && l.list.currentFocus == vis.id)
  623. }
  624. }
  625. // nil out all references before returning slices to pool
  626. for i := 0; i < len(wasVisible); i++ {
  627. wasVisible[i].item = nil
  628. }
  629. for i := 0; i < len(visible); i++ {
  630. visible[i].item = nil
  631. }
  632. *wasVisiblePtr = wasVisible // Copy the stack header over to the heap
  633. *visiblePtr = visible
  634. l.slicePool.Put(wasVisiblePtr)
  635. l.slicePool.Put(visiblePtr)
  636. }
  637. func (l *listLayout) updateSeparators() {
  638. if lenChildren := len(l.children); lenChildren > 1 {
  639. if lenSep := len(l.separators); lenSep > lenChildren {
  640. l.separators = l.separators[:lenChildren]
  641. } else {
  642. for i := lenSep; i < lenChildren; i++ {
  643. l.separators = append(l.separators, NewSeparator())
  644. }
  645. }
  646. } else {
  647. l.separators = nil
  648. }
  649. separatorThickness := theme.SeparatorThicknessSize()
  650. dividerOff := (theme.Padding() + separatorThickness) / 2
  651. for i, child := range l.children {
  652. if i == 0 {
  653. continue
  654. }
  655. l.separators[i].Move(fyne.NewPos(0, child.Position().Y-dividerOff))
  656. l.separators[i].Resize(fyne.NewSize(l.list.Size().Width, separatorThickness))
  657. l.separators[i].Show()
  658. }
  659. }
  660. // invariant: visible is in ascending order of IDs
  661. func (l *listLayout) searchVisible(visible []listItemAndID, id ListItemID) (*listItem, bool) {
  662. ln := len(visible)
  663. idx := sort.Search(ln, func(i int) bool { return visible[i].id >= id })
  664. if idx < ln && visible[idx].id == id {
  665. return visible[idx].item, true
  666. }
  667. return nil, false
  668. }
  669. func (l *listLayout) nilOldSliceData(objs []fyne.CanvasObject, len, oldLen int) {
  670. if oldLen > len {
  671. objs = objs[:oldLen] // gain view into old data
  672. for i := len; i < oldLen; i++ {
  673. objs[i] = nil
  674. }
  675. }
  676. }
  677. func (l *listLayout) nilOldVisibleSliceData(objs []listItemAndID, len, oldLen int) {
  678. if oldLen > len {
  679. objs = objs[:oldLen] // gain view into old data
  680. for i := len; i < oldLen; i++ {
  681. objs[i].item = nil
  682. }
  683. }
  684. }