diff --git a/.gitignore b/.gitignore index dc0c464..8de6c13 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ examples/textinputs/textinputs examples/views/views tutorials/basics/basics tutorials/commands/commands +.idea diff --git a/examples/altscreen-toggle/main.go b/examples/altscreen-toggle/main.go new file mode 100644 index 0000000..2795b24 --- /dev/null +++ b/examples/altscreen-toggle/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "os" + + tea "github.com/charmbracelet/bubbletea" + "github.com/muesli/termenv" +) + +var ( + color = termenv.ColorProfile().Color + keyword = termenv.Style{}.Foreground(color("204")).Background(color("235")).Styled +) + +type model struct { + altscreen bool + quitting bool +} + +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: + switch msg.String() { + case "q", "ctrl+c", "esc": + m.quitting = true + return m, tea.Quit + case " ": + var cmd tea.Cmd + if m.altscreen { + cmd = tea.ExitAltScreen + } else { + cmd = tea.EnterAltScreen + } + m.altscreen = !m.altscreen + return m, cmd + } + } + return m, nil +} + +func (m model) View() string { + if m.quitting { + return "Bye!\n" + } + + const ( + altscreenMode = " altscreen mode " + inlineMode = " inline mode " + ) + + var mode, otherMode string + if m.altscreen { + mode = altscreenMode + otherMode = inlineMode + } else { + mode = inlineMode + otherMode = altscreenMode + } + + return fmt.Sprintf( + "\n You're in %s. Press %s to swich to %s.\n\n To exit press %s.\n", + keyword(mode), keyword(" space "), keyword(otherMode), keyword(" q "), + ) +} + +func main() { + if err := tea.NewProgram(model{}).Start(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} diff --git a/examples/fullscreen/main.go b/examples/fullscreen/main.go index 0a7e293..4ad9464 100644 --- a/examples/fullscreen/main.go +++ b/examples/fullscreen/main.go @@ -18,9 +18,9 @@ type tickMsg time.Time func main() { p := tea.NewProgram(model(5)) + // Bubble Tea will automatically exit the alternate screen buffer. p.EnterAltScreen() err := p.Start() - p.ExitAltScreen() if err != nil { log.Fatal(err) diff --git a/tea.go b/tea.go index cd82ce2..af6fef8 100644 --- a/tea.go +++ b/tea.go @@ -168,6 +168,26 @@ func Quit() Msg { // send a quitMsg with Quit. type quitMsg struct{} +// EnterAltScreen is a special command that tells the Bubble Tea program to enter +// alternate screen buffer. +func EnterAltScreen() Msg { + return enterAltScreenMsg{} +} + +// enterAltScreenMsg in an internal message signals that the program should enter +// alternate screen buffer. You can send a enterAltScreenMsg with EnterAltScreen. +type enterAltScreenMsg struct{} + +// ExitAltScreen is a special command that tells the Bubble Tea program to exit +// alternate screen buffer. +func ExitAltScreen() Msg { + return exitAltScreenMsg{} +} + +// exitAltScreenMsg in an internal message signals that the program should exit +// alternate screen buffer. You can send a exitAltScreenMsg with ExitAltScreen. +type exitAltScreenMsg struct{} + // batchMsg is the internal message used to perform a bunch of commands. You // can send a batchMsg with Batch. type batchMsg []Cmd @@ -366,9 +386,14 @@ func (p *Program) Start() error { // Handle special messages switch msg.(type) { case quitMsg: + p.ExitAltScreen() p.renderer.stop() close(done) return nil + case enterAltScreenMsg: + p.EnterAltScreen() + case exitAltScreenMsg: + p.ExitAltScreen() case hideCursorMsg: hideCursor(p.output) } @@ -399,6 +424,11 @@ func (p *Program) Start() error { func (p *Program) EnterAltScreen() { p.mtx.Lock() defer p.mtx.Unlock() + + if p.altScreenActive { + return + } + fmt.Fprintf(p.output, te.CSI+te.AltScreenSeq) moveCursor(p.output, 0, 0) @@ -412,6 +442,11 @@ func (p *Program) EnterAltScreen() { func (p *Program) ExitAltScreen() { p.mtx.Lock() defer p.mtx.Unlock() + + if !p.altScreenActive { + return + } + fmt.Fprintf(p.output, te.CSI+te.ExitAltScreenSeq) p.altScreenActive = false