Screens and navigation¶
The Screen interface¶
type Screen interface {
Title() string
Init() tea.Cmd
Update(msg tea.Msg) (Screen, tea.Cmd)
Layout() layout.Node
Help() []key.Binding
OnEnter(result any) tea.Cmd
SetTheme(t theme.Theme)
IsCapturingKeys() bool
}
| Method | Called | Notes |
|---|---|---|
Title |
every render | Its crumb in the breadcrumb. Free to change, e.g. "Pods › " + selected. |
Init |
once, when the screen is pushed | Usually nil, or the batched Init of a poll or focus group. |
Update |
every message routed to the screen | Return the same pointer and any command. |
Layout |
every render | Return a layout.Node; the shell renders it in the body rect. |
Help |
every render | Bindings for the footer and the overlay. |
OnEnter |
each time the screen becomes the top | result is nil on push, or whatever a child popped with. Start fetches here. |
SetTheme |
before first render, and on every theme change | Rebuild themed components, carrying State. |
IsCapturingKeys |
before global keys are handled | True while a text field or filter owns input. |
Implement screens as pointer types so Update, OnEnter and SetTheme can
mutate fields. Optional interfaces add more:
| Interface | Method | Adds |
|---|---|---|
help.Sectioned |
HelpSections() []help.Section |
a grouped ? overlay (Help) |
action.Provider |
Actions() action.Set |
an action menu (Verbs) |
Pushing, popping, replacing¶
All stack operations are commands returned from Update:
return s, screen.Push(newDetail(item, s.t)) // open a child
return s, screen.Pop(chosen) // close, handing a value back
return s, screen.Pop(nil) // close without a value
return s, screen.Replace(newList(s.t)) // swap the top for a fresh instance
return s, screen.PopTo(1, nil) // unwind to the root
| Command | Effect |
|---|---|
Push(s) |
s.Init() and s.OnEnter(nil) run; s becomes the top. |
Pop(result) |
The top is discarded; the uncovered screen's OnEnter(result) runs. |
PopTo(depth, result) |
Unwinds to depth screens (1 = root). Screens in between are discarded without being activated. A depth at or above the current one is a no-op. |
Replace(s) |
Swaps the top for s in one step. s.Init and s.OnEnter(nil) run; the screen below is not notified. |
Push and Replace don't call SetTheme: construct the new screen with its theme
already applied. Keep the current theme in a field (s.t) for this.
Under the shell, esc pops automatically when the stack is deeper than one and
no screen is capturing keys, and clicking a crumb in the breadcrumb is a
PopTo.
Passing data¶
Parent to child: pass it to the child's constructor.
if s.list.IsActivate(msg) {
if city, ok := s.list.Selected(); ok {
return s, screen.Push(newCityDetail(city, s.t))
}
}
Child to parent: pop with a value, and match it in the parent's OnEnter.
// in the picker
return s, screen.Pop(tz)
// in the parent
func (s *cityDetail) OnEnter(result any) tea.Cmd {
if tz, ok := result.(string); ok && tz != "" {
s.chosen = tz
s.rebuildInfo()
}
return nil
}
OnEnter and fetches¶
OnEnter is where a screen starts work it needs whenever it's shown, such as
a fetch. Because Pop always fires OnEnter, a screen that fetches there
must ignore the pop from the output console, or glancing at the log refetches
silently:
func (s *Screen) OnEnter(result any) tea.Cmd {
if _, closed := result.(app.OutputClosed); closed {
return nil // a glance at the log is not a fresh activation
}
return s.fetch()
}
Fresh instances with Replace¶
Use Replace when the user wants "this view, from scratch": reset filter and
scroll, refetch. A Pop followed by a Push flickers and fires OnEnter on
the parent, which may start a fetch the user never asked for.
A login-style prescreen¶
A root screen can push a child from its own OnEnter (for a login form, say),
take the result when it pops, and push it again later on logout. The child
never lives permanently on the stack. See examples/shell/prescreen.
Messages reach covered screens¶
Keys and mouse go to the top screen only. Every other message (timers, fetch results, spinner ticks) reaches every screen on the stack. A covered screen therefore keeps its poll chain and spinners alive and is up to date when you return to it. Don't gate a screen's timers on being visible.
Tabs are not navigation¶
pkg/tab hosts several screens behind a one-row strip inside a single host
screen. It never touches the stack. See Tabs.