Long lists
Show thousands of rows of components with List, building only those in view, keep a cursor in view, page by what fit, and scroll with the wheel and a scrollbar.
bloom.List(count, item) shows as many of count items as fit, top to bottom. It calls item(i) only for the
items it may show, so a list of a million rows costs no more to draw than the rows on screen. Each item is as wide
as the list and as tall as it wants, and the last one that doesn't fit is cut off:
bloom.List(len(m.logs), func(i int) bloom.Child {
return bloom.Text(m.logs[i]).Style(line)
}).Offset(m.offset).ID(paneLogs)Offset(i) is the first item shown. The list clamps it, so an offset past the end shows the last page, full,
rather than space below it. Gap(n) puts n cells between items.
Use a list for rows of components, such as a file picker, a table, or a feed with items of different heights. For lines of text, a viewport does more, such as wrapping, search, and selecting text scrolled out of sight.
Keep the cursor in view
Keep(i) scrolls the list as little as it can from its offset so that item i is shown whole. Give it the item
under a cursor, and moving the cursor is all the model does:
func (m *model) layout() bloom.Child {
return bloom.List(len(m.items), m.row).Offset(m.offset).Keep(m.cursor).ID(paneItems)
}Read where it ended up
Items can have different heights, so the list, not the model, knows where it ended up. Read it back with
Frame().List(id), and keep First as the offset to scroll from next time. Visible is how many items were
shown whole, which is how far to page:
case tea.KeyPressMsg:
v, _ := m.ui.Frame().List(paneItems)
m.offset = v.First
switch msg.String() {
case "down":
m.cursor++
case "pgdown":
m.cursor += max(v.Visible-1, 1)
}
m.cursor = min(max(m.cursor, 0), len(m.items)-1)Read the frame when the key arrives, as above, rather than keeping what it said when the last key was pressed: a resize, or the list keeping the cursor in view, moves it without a key.
Scroll with the wheel
While Keep is given, the list can't scroll away from that item, so to scroll with the wheel, move the cursor
along with the offset:
bloom.List(len(m.items), m.row).Offset(m.offset).Keep(m.cursor).ID(paneItems).
On(on.Scroll(func(_, dy int) tea.Cmd {
v, _ := m.ui.Frame().List(paneItems)
m.offset = max(v.First+dy*3, 0)
m.cursor = min(max(m.cursor, m.offset), m.offset+v.Visible-1, len(m.items)-1)
return nil
}))Or let the wheel move only the cursor, m.cursor += dy * 3, and the list follows it as it does the arrow keys.
Items are followed from frame to frame by their index, so mouse handlers and hover styles stay with an item as the list scrolls.
Show a scrollbar
ScrollbarOf the list's own ID follows it in items, and its OnScroll gets the first item to show, for Offset:
bloom.Box(panel, bloom.List(len(m.items), m.row).Offset(m.offset).ID(paneItems)).
Edge(bloom.Right, bloom.Stretch, bloom.ScrollbarOf(paneItems).OnScroll(func(first int) tea.Cmd {
m.offset = first
return nil
}))See Show a scrollbar for how scrollbars look and work.
Lists in components
A component that implements Layouter has no root
to ask, so its handlers read the frame from the Context its Layout was given. ctx.Frame() returns the frame on
screen when it's called, which is the one the handler's event landed in:
func (p *picker) Layout(ctx bloom.Context) bloom.Child {
return bloom.List(len(p.items), p.row).Offset(p.offset).Keep(p.cursor).ID(p.id).
On(on.Scroll(func(_, dy int) tea.Cmd {
v, _ := ctx.Frame().List(p.id)
p.offset = v.First
p.cursor = min(max(p.cursor+dy*3, 0), len(p.items)-1)
return nil
}))
}Select text
Selectable() lets the mouse select text in the list. A list lays out only the items in view, so a selection takes
only those, and ends when they scroll away. See Selecting and copying text.
Related
- Viewports, for lines of text
- Scrolling, for
Scrolland scrollbars - Focus and keyboard navigation
ListAPI reference
Last updated on
Viewports
Show logs, files, and other long text with Viewport and Buffer, drawing only the lines in sight, with following, wrapping, line numbers, search, styles, and selection.
Sizes and positions in Update
Find where a node goes, and how big it is, from Update, with Solve for the state you have now or Frame for what's on screen, for paging, clamping, and components that need their size up front.