bubbletea/examples/views/main.go

224 lines
4.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"math"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/fogleman/ease"
)
func main() {
p := tea.NewProgram(
initialize,
update,
view,
)
if err := p.Start(); err != nil {
fmt.Println("could not start program:", err)
}
}
// MESSAGES
type tickMsg struct{}
type frameMsg struct{}
// MODEL
type model struct {
Choice int
Chosen bool
Ticks int
Frames int
Progress float64
Loaded bool
}
func initialize() (tea.Model, tea.Cmd) {
return model{0, false, 10, 0, 0, false}, tick()
}
// CMDS
func tick() tea.Cmd {
return tea.Tick(time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}
func frame() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return frameMsg{}
})
}
// UPDATE
// Main update function. This just hands off the message and model to the
// approprate update loop based on the current state.
func update(msg tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) {
m, _ := mdl.(model)
if !m.Chosen {
return updateChoices(msg, m)
}
return updateChosen(msg, m)
}
// Update loop for the first view where you're choosing a task
func updateChoices(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "j":
fallthrough
case "down":
m.Choice += 1
if m.Choice > 3 {
m.Choice = 3
}
case "k":
fallthrough
case "up":
m.Choice -= 1
if m.Choice < 0 {
m.Choice = 0
}
case "enter":
m.Chosen = true
return m, frame()
case "q":
fallthrough
case "esc":
fallthrough
case "ctrl+c":
return m, tea.Quit
}
case tickMsg:
if m.Ticks == 0 {
return m, tea.Quit
}
m.Ticks -= 1
return m, tick()
}
return m, nil
}
// Update loop for the second view after a choice has been made
func updateChosen(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q":
fallthrough
case "esc":
fallthrough
case "ctrl+c":
return m, tea.Quit
}
case frameMsg:
if !m.Loaded {
m.Frames += 1
m.Progress = ease.OutBounce(float64(m.Frames) / float64(100))
if m.Progress >= 1 {
m.Progress = 1
m.Loaded = true
m.Ticks = 3
return m, tick()
}
return m, frame()
}
case tickMsg:
if m.Loaded {
if m.Ticks == 0 {
return m, tea.Quit
}
m.Ticks -= 1
return m, tick()
}
}
return m, nil
}
// VIEW
// The main view, which just calls the approprate sub-view
func view(mdl tea.Model) string {
m, _ := mdl.(model)
if !m.Chosen {
return choicesView(m) + "\n"
}
return chosenView(m) + "\n"
}
// The first view, where you're choosing a task
func choicesView(m model) string {
c := m.Choice
tpl := "What to do today?\n\n"
tpl += "%s\n\n"
tpl += "Program quits in %d seconds\n\n"
tpl += "(press j/k or up/down to select, enter to choose, and q or esc to quit)`"
choices := fmt.Sprintf(
"%s\n%s\n%s\n%s",
checkbox("Plant carrots", c == 0),
checkbox("Go to the market", c == 1),
checkbox("Read something", c == 2),
checkbox("See friends", c == 3),
)
return fmt.Sprintf(tpl, choices, m.Ticks)
}
// The second view, after a task has been chosen
func chosenView(m model) string {
var msg string
switch m.Choice {
case 0:
msg = "Carrot planting?\n\nCool, we'll need libgarden and vegeutils..."
case 1:
msg = "A trip to the market?\n\nOkay, then we should install marketkit and libshopping..."
case 2:
msg = "Reading time?\n\nOkay, cool, then well need a library. Yes, an actual library."
default:
msg = "Its always good to see friends.\n\nFetching social-skills and conversationutils..."
}
label := "Downloading..."
if m.Loaded {
label = fmt.Sprintf("Downloaded. Exiting in %d...", m.Ticks)
}
return msg + "\n\n " + label + "\n" + progressbar(80, m.Progress) + "%"
}
func checkbox(label string, checked bool) string {
check := " "
if checked {
check = "x"
}
return fmt.Sprintf("[%s] %s", check, label)
}
func progressbar(width int, percent float64) string {
metaChars := 7
w := float64(width - metaChars)
fullSize := int(math.Round(w * percent))
emptySize := int(w) - fullSize
fullCells := strings.Repeat("#", fullSize)
emptyCells := strings.Repeat(".", emptySize)
return fmt.Sprintf("|%s%s| %3.0f", fullCells, emptyCells, math.Round(percent*100))
}