Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce updateItem calls #4012

Merged
merged 18 commits into from
Jul 11, 2023
42 changes: 35 additions & 7 deletions widget/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ func (l *List) MinSize() fyne.Size {
return l.BaseWidget.MinSize()
}

// RefreshItem refreshes a single item, specified by the item ID passed in.
//
// Since: 2.4
func (l *List) RefreshItem(id ListItemID) {
SMQuazi marked this conversation as resolved.
Show resolved Hide resolved
if l.scroller == nil {
return
}
l.BaseWidget.Refresh()
lo := l.scroller.Content.(*fyne.Container).Layout.(*listLayout)
visible := lo.visible
canvas := fyne.CurrentApp().Driver().CanvasForObject(lo.list)
var focused fyne.Focusable
if canvas != nil {
focused = canvas.Focused()
}
if item, ok := visible[id]; ok {
lo.setupListItem(item, id, focused == item)
}
}

// SetItemHeight supports changing the height of the specified list item. Items normally take the height of the template
// returned from the CreateItem callback. The height parameter uses the same units as a fyne.Size type and refers
// to the internal content height not including the divider size.
Expand All @@ -110,7 +130,7 @@ func (l *List) SetItemHeight(id ListItemID, height float32) {
l.propertyLock.Unlock()

if refresh {
l.Refresh()
l.RefreshItem(id)
}
}

Expand Down Expand Up @@ -152,7 +172,7 @@ func (l *List) Resize(s fyne.Size) {
}

l.offsetUpdated(l.scroller.Offset)
l.scroller.Content.(*fyne.Container).Layout.(*listLayout).updateList(true)
l.scroller.Content.(*fyne.Container).Layout.(*listLayout).updateList(false)
}

// Select add the item identified by the given ID to the selection.
Expand Down Expand Up @@ -344,7 +364,7 @@ func (l *listRenderer) Refresh() {
}
l.Layout(l.list.Size())
l.scroller.Refresh()
l.layout.Layout.(*listLayout).updateList(true)
l.layout.Layout.(*listLayout).updateList(false)
canvas.Refresh(l.list.super())
}

Expand Down Expand Up @@ -556,7 +576,7 @@ func (l *listLayout) offsetUpdated(pos fyne.Position) {
return
}
l.list.offsetY = pos.Y
l.updateList(false)
l.updateList(true)
}

func (l *listLayout) setupListItem(li *listItem, id ListItemID, focus bool) {
Expand All @@ -583,7 +603,7 @@ func (l *listLayout) setupListItem(li *listItem, id ListItemID, focus bool) {
}
}

func (l *listLayout) updateList(refresh bool) {
func (l *listLayout) updateList(newOnly bool) {
l.renderLock.Lock()
separatorThickness := theme.Padding()
width := l.list.Size().Width
Expand Down Expand Up @@ -654,8 +674,16 @@ func (l *listLayout) updateList(refresh bool) {
l.list.scroller.Content.(*fyne.Container).Objects = objects
l.renderLock.Unlock() // user code should not be locked

for row, obj := range visible {
l.setupListItem(obj, row, focused == obj)
if newOnly {
for row, obj := range visible {
if _, ok := wasVisible[row]; !ok {
l.setupListItem(obj, row, focused == obj)
}
}
} else {
for row, obj := range visible {
l.setupListItem(obj, row, focused == obj)
}
}
}

Expand Down
60 changes: 60 additions & 0 deletions widget/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,19 @@ func TestList_DataChange(t *testing.T) {
test.AssertRendersToMarkup(t, "list/new_data.xml", w.Canvas())
}

func TestList_ItemDataChange(t *testing.T) {
test.NewApp()
defer test.NewApp()

list, _ := setupList(t)
children := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children
assert.Equal(t, children[0].(*listItem).child.(*fyne.Container).Objects[1].(*Label).Text, "Test Item 0")
changeData(list)
list.RefreshItem(0)
children = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children
assert.Equal(t, children[0].(*listItem).child.(*fyne.Container).Objects[1].(*Label).Text, "a")
}

func TestList_ThemeChange(t *testing.T) {
defer test.NewApp()
list, w := setupList(t)
Expand Down Expand Up @@ -578,3 +591,50 @@ func setupList(t *testing.T) (*List, fyne.Window) {
test.AssertRendersToMarkup(t, "list/initial.xml", w.Canvas())
return list, w
}

func TestList_LimitUpdateItem(t *testing.T) {
app := test.NewApp()
w := app.NewWindow("")
printOut := ""
list := NewList(
func() int {
return 5
},
func() fyne.CanvasObject {
return NewLabel("")
},
func(id ListItemID, item fyne.CanvasObject) {
printOut += fmt.Sprintf("%d.", id)
},
)
w.SetContent(list)
w.ShowAndRun()
assert.Equal(t, "0.0.", printOut)
list.scrollTo(1)
assert.Equal(t, "0.0.1.", printOut)
list.scrollTo(2)
assert.Equal(t, "0.0.1.2.", printOut)
}

func TestList_RefreshUpdatesAllItems(t *testing.T) {
app := test.NewApp()
w := app.NewWindow("")
printOut := ""
list := NewList(
func() int {
return 1
},
func() fyne.CanvasObject {
return NewLabel("Test")
},
func(id ListItemID, item fyne.CanvasObject) {
printOut += fmt.Sprintf("%d.", id)
},
)
w.SetContent(list)
w.ShowAndRun()
assert.Equal(t, "0.", printOut)

list.Refresh()
assert.Equal(t, "0.0.", printOut)
}