M list/async.go => list/async.go +3 -2
@@ 125,10 125,11 @@ func asyncProcess(maxSize int, hooks Hooks) (chan<- interface{}, chan viewport,
loadSerial = synthesis.SerialAt(len(synthesis.Source) - 1)
}
// Load new elements.
- newElems = append(newElems, hooks.Loader(req.Direction, loadSerial)...)
+ var more bool
+ newElems, more = hooks.Loader(req.Direction, loadSerial)
// Track whether all new elements in a given direction have been
// exhausted.
- if len(newElems) == 0 {
+ if len(newElems) == 0 || !more {
ignore.Add(req.Direction)
} else {
ignore = NoDirection
M list/async_test.go => list/async_test.go +15 -6
@@ 20,14 20,15 @@ var testElements = func() []Element {
func TestAsyncProcess(t *testing.T) {
var nextLoad []Element
+ var more bool
var loadInvoked bool
hooks := Hooks{
Invalidator: func() {},
Comparator: testComparator,
Synthesizer: testSynthesizer,
- Loader: func(dir Direction, rt Serial) []Element {
+ Loader: func(dir Direction, rt Serial) ([]Element, bool) {
loadInvoked = true
- return nextLoad
+ return nextLoad, more
},
}
size := 6
@@ 40,6 41,9 @@ func TestAsyncProcess(t *testing.T) {
input loadRequest
// the data that will be returned by the data request (if the loader is executed)
load []Element
+ // whether the async logic should expect additional content in the direction of
+ // the load.
+ loadMore bool
// should the testcase block waiting for an update on the update channel
skipUpdate bool
// the update to expect on the update channel
@@ 60,7 64,8 @@ func TestAsyncProcess(t *testing.T) {
},
Direction: Before,
},
- load: testElements[7:],
+ load: testElements[7:],
+ loadMore: true,
expected: stateUpdate{
Synthesis: Synthesis{
Elements: testElements[7:],
@@ 119,7 124,8 @@ func TestAsyncProcess(t *testing.T) {
},
Direction: Before,
},
- load: testElements[4:7],
+ load: testElements[4:7],
+ loadMore: true,
expected: stateUpdate{
Synthesis: Synthesis{
Elements: testElements[4:],
@@ 172,7 178,8 @@ func TestAsyncProcess(t *testing.T) {
},
Direction: Before,
},
- load: testElements[1:4],
+ load: testElements[1:4],
+ loadMore: true,
expected: stateUpdate{
Synthesis: Synthesis{
Elements: testElements[3:9],
@@ 201,7 208,8 @@ func TestAsyncProcess(t *testing.T) {
},
Direction: Before,
},
- load: testElements[:3],
+ load: testElements[:3],
+ loadMore: true,
expected: stateUpdate{
Synthesis: Synthesis{
Elements: testElements[2:8],
@@ 267,6 275,7 @@ func TestAsyncProcess(t *testing.T) {
// ensure that the next invocation of the loader will load this
// testcase's data payload.
nextLoad = tc.load
+ more = tc.loadMore
// request a load
reqs <- tc.input
M list/element.go => list/element.go +6 -5
@@ 58,14 58,15 @@ type Synthesizer func(previous, current, next Element) []Element
type Comparator func(a, b Element) bool
// Loader is a function that can fulfill load requests. If it returns
-// a response with no elements in a given direction, the manager will not
+// a response with no elements in a given direction or false as its
+// second return value, the manager will not
// invoke the loader in that direction again until the manager loads
// data from the other end of the list or another manger state update
// occurs.
//
// Loader implements pull modifications. When the manager wants more data it
// will invoke the Loader hook to get more.
-type Loader func(direction Direction, relativeTo Serial) []Element
+type Loader func(direction Direction, relativeTo Serial) (elems []Element, more bool)
// Presenter is a function that can transform the data for an Element
// into a widget to be laid out in the user interface. It must not return
@@ 121,11 122,11 @@ func DefaultHooks(w *app.Window, th *material.Theme) Hooks {
Comparator: func(a, b Element) bool {
return string(a.Serial()) < string(b.Serial())
},
- Loader: func(dir Direction, relativeTo Serial) []Element {
+ Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) {
if relativeTo == NoSerial {
- return newDefaultElements()
+ return newDefaultElements(), false
}
- return nil
+ return nil, false
},
Presenter: func(elem Element, state interface{}) layout.Widget {
return material.H4(th, "Implement list.Hooks to change me.").Layout
M list/manager.go => list/manager.go +2 -2
@@ 333,8 333,8 @@ func (m *Manager) UpdatedLen(list *layout.List) int {
// the list's position.
firstElementVisible := list.Position.First == 0
lastElementVisible := list.Position.First+list.Position.Count == len(m.elements.Elements)
- stickToEnd := lastElementVisible && m.Stickiness.Contains(After) && m.ignoring.Contains(After)
- stickToBeginning := firstElementVisible && m.Stickiness.Contains(Before) && m.ignoring.Contains(Before)
+ stickToEnd := lastElementVisible && m.Stickiness.Contains(After) && (m.ignoring.Contains(After) || su.Type == push)
+ stickToBeginning := firstElementVisible && m.Stickiness.Contains(Before) && (m.ignoring.Contains(Before) || su.Type == push)
if !stickToBeginning {
// Update the list position to match the new set of elements.
M list/manager_test.go => list/manager_test.go +5 -6
@@ 67,8 67,8 @@ func TestManager(t *testing.T) {
Height: unit.Dp(5),
}.Layout
},
- Loader: func(dir Direction, relativeTo Serial) []Element {
- return nil
+ Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) {
+ return nil, false
},
Invalidator: func() {},
Comparator: func(a, b Element) bool { return true },
@@ 381,7 381,6 @@ func TestManagerPrefetch(t *testing.T) {
}
default:
}
-
})
}
}
@@ 420,7 419,7 @@ func TestManagerViewportOnRemoval(t *testing.T) {
Height: unit.Dp(5),
}.Layout
},
- Loader: func(dir Direction, relativeTo Serial) []Element { return nil },
+ Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) { return nil, false },
Invalidator: func() {},
Comparator: func(a, b Element) bool { return true },
Synthesizer: synth,
@@ 559,8 558,8 @@ var testHooks = Hooks{
Height: unit.Dp(5),
}.Layout
},
- Loader: func(dir Direction, relativeTo Serial) []Element {
- return nil
+ Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) {
+ return nil, false
},
Invalidator: func() {},
Comparator: func(a, b Element) bool { return true },