charmbracelet / bubbletea

A powerful little TUI framework 🏗

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

View() called before WindowSizeMsg

Robert-M-Muench opened this issue · comments

Note that WindowSizeMsg is sent before the first render and then again every resize.

Originally posted by @meowgorithm in #43 (comment)

My current program's View() function gets called before I get a WindowSizeMsg which makes it impossible to know how to size my output. How can this be possible?

This is the call stack when View()is called:
image

And this is the call stack when Update() is called:
image

You can see that the Update() (line: 547) comes later in the tea.go.StartRetruningModel code as the View() (line: 405) call.

Hi! The query for the initial window size runs asynchronously so that it doesn't hold up the program startup (since many programs don't need the window size). As a result, it's very possible that the initial view can be called before the window size is queried. If your program does depend on knowing the window size the general pattern is to set a flag on your model when the window size comes in and manage output in your view accordingly.

Here’s a contrived example of how it's commonly done with a notion of application state:

package main

import (
	"fmt"
	"os"

	tea "github.com/charmbracelet/bubbletea"
)

type state int

const (
	initializing state = iota
	ready
)

type model struct {
	state         state
	width, height int
}

func (m model) Init() tea.Cmd {
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		return m, tea.Quit
	case tea.WindowSizeMsg:
		m.width, m.height = msg.Width, msg.Height
		m.state = ready
	}
	return m, nil
}

func (m model) View() string {
	if m.state == initializing {
		return "Initializing..."
	}
	return fmt.Sprintf("This is a %dx%d size terminal.", m.width, m.height)
}

func main() {
	if err := tea.NewProgram(model{}).Start(); err != nil {
		fmt.Println("uh oh:", err)
		os.Exit(1)
	}
}

Of course, for simple programs you could eliminate the state portion and just check and see if width or height is greater than 0.

Ok, thanks. The async explains it. Not sure if this is mentioned in the docs somewhere. If not, I think it's worth doing.