@@ -75,30 +75,24 @@ func (l *List) Gap() int {
75 return l.gap
75 return l.gap
78−// AtBottom returns whether the list is scrolled to the bottom.
78+// AtBottom returns whether the list is showing the last item at the bottom.
79func (l *List) AtBottom() bool {
79func (l *List) AtBottom() bool {
80 if len(l.items) == 0 {
80 if len(l.items) == 0 {
81 return true
81 return true
84− // Calculate total height of all items from the bottom.
84+ // Calculate the height from offsetIdx to the end.
85 var totalHeight int
85 var totalHeight int
86− for i := len(l.items) - 1; i >= 0; i-- {
86+ for idx := l.offsetIdx; idx < len(l.items); idx++ {
87− item := l.getItem(i)
87+ item := l.getItem(idx)
88− totalHeight += item.height
88+ itemHeight := item.height
89− if l.gap > 0 && i < len(l.items)-1 {
89+ if l.gap > 0 && idx > l.offsetIdx {
90− totalHeight += l.gap
90+ itemHeight += l.gap
92− if totalHeight >= l.height {
93− // This is the expected bottom position.
95− expectedLine := totalHeight - l.height
96− return l.offsetIdx == expectedIdx && l.offsetLine >= expectedLine
92+ totalHeight += itemHeight
100− // All items fit in viewport - we're at bottom if at top.
101− return l.offsetIdx == 0 && l.offsetLine == 0
95+ return totalHeight-l.offsetLine <= l.height
104// SetReverse shows the list in reverse order.
98// SetReverse shows the list in reverse order.
@@ -121,6 +115,30 @@ func (l *List) Len() int {
121 return len(l.items)
115 return len(l.items)
118+// lastOffsetItem returns the index and line offsets of the last item that can
119+// be partially visible in the viewport.
120+func (l *List) lastOffsetItem() (int, int, int) {
123+ for idx = len(l.items) - 1; idx >= 0; idx-- {
124+ item := l.getItem(idx)
125+ itemHeight := item.height
126+ if l.gap > 0 && idx < len(l.items)-1 {
129+ totalHeight += itemHeight
130+ if totalHeight > l.height {
135+ // Calculate line offset within the item
136+ lineOffset := max(totalHeight-l.height, 0)
139+ return idx, lineOffset, totalHeight
124// getItem renders (if needed) and returns the item at the given index.
142// getItem renders (if needed) and returns the item at the given index.
125func (l *List) getItem(idx int) renderedItem {
143func (l *List) getItem(idx int) renderedItem {
126 if idx < 0 || idx >= len(l.items) {
144 if idx < 0 || idx >= len(l.items) {
@@ -171,44 +189,29 @@ func (l *List) ScrollBy(lines int) {
172 if lines > 0 {
190 if lines > 0 {
173 // Scroll down
191 // Scroll down
174− // Calculate from the bottom how many lines needed to anchor the last
175− // item to the bottom
177− var lastItemIdx int // the last item that can be partially visible
178− for i := len(l.items) - 1; i >= 0; i-- {
179− item := l.getItem(i)
180− totalLines += item.height
181− if l.gap > 0 && i < len(l.items)-1 {
184− if totalLines > l.height-1 {
190− // Now scroll down by lines
191− var item renderedItem
192 l.offsetLine += lines
192 l.offsetLine += lines
194− item = l.getItem(l.offsetIdx)
193+ currentItem := l.getItem(l.offsetIdx)
195− totalHeight := item.height
194+ for l.offsetLine >= currentItem.height {
195+ l.offsetLine -= currentItem.height
196 if l.gap > 0 {
196 if l.gap > 0 {
197− totalHeight += l.gap
197+ l.offsetLine -= l.gap
200− if l.offsetIdx >= lastItemIdx || l.offsetLine < totalHeight {
205 // Move to next item
200 // Move to next item
206− l.offsetLine -= totalHeight
207 l.offsetIdx++
201 l.offsetIdx++
202+ if l.offsetIdx > len(l.items)-1 {
207+ currentItem = l.getItem(l.offsetIdx)
210− if l.offsetLine >= item.height {
210+ lastOffsetIdx, lastOffsetLine, _ := l.lastOffsetItem()
211+ if l.offsetIdx > lastOffsetIdx || (l.offsetIdx == lastOffsetIdx && l.offsetLine > lastOffsetLine) {
213+ l.offsetIdx = lastOffsetIdx
211− l.offsetLine = item.height
214+ l.offsetLine = lastOffsetLine
213 } else if lines < 0 {
216 } else if lines < 0 {
214 // Scroll up
217 // Scroll up
@@ -408,24 +411,9 @@ func (l *List) ScrollToBottom() {
411− // Scroll to the last item
414+ lastOffsetIdx, lastOffsetLine, _ := l.lastOffsetItem()
413− for i := len(l.items) - 1; i >= 0; i-- {
414− item := l.getItem(i)
415− totalHeight += item.height
416− if l.gap > 0 && i < len(l.items)-1 {
417− totalHeight += l.gap
419− if totalHeight >= l.height {
420− l.offsetIdx = i
415+ l.offsetIdx = lastOffsetIdx
421− l.offsetLine = totalHeight - l.height
416+ l.offsetLine = lastOffsetLine
425− if totalHeight < l.height {
426− // All items fit in the viewport
431// ScrollToSelected scrolls the list to the selected item.
419// ScrollToSelected scrolls the list to the selected item.