list.go 17 KB

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