bubbletea/examples/views/main.go

231 lines
4.0 KiB
Go
Raw Normal View History

2020-01-10 16:02:04 -05:00
package main
// TODO: This code feels messy. Clean it up.
2020-01-10 16:02:04 -05:00
import (
"fmt"
2020-01-14 13:47:36 -05:00
"math"
"strings"
"time"
2020-01-14 13:47:36 -05:00
2020-01-18 11:15:55 -05:00
"github.com/charmbracelet/tea"
2020-01-14 13:47:36 -05:00
"github.com/fogleman/ease"
2020-01-10 16:02:04 -05:00
)
func main() {
p := tea.NewProgram(
initialize,
update,
view,
subscriptions,
)
if err := p.Start(); err != nil {
fmt.Println("could not start program:", err)
}
2020-01-10 16:02:04 -05:00
}
// MSG
type tickMsg time.Time
func newTickMsg(t time.Time) tea.Msg {
return tickMsg(t)
}
type frameMsg time.Time
func newFrameMsg(t time.Time) tea.Msg {
return frameMsg(t)
}
// MODEL
// Model contains the data for our application.
type Model struct {
Choice int
Chosen bool
Ticks int
Frames int
Progress float64
Loaded bool
}
// INIT
func initialize() (tea.Model, tea.Cmd) {
return Model{0, false, 10, 0, 0, false}, nil
}
2020-01-14 13:47:36 -05:00
// SUBSCRIPTIONS
func subscriptions(model tea.Model) tea.Subs {
m, _ := model.(Model)
if !m.Chosen || m.Loaded {
return tea.Subs{
"tick": tea.Every(time.Second, newTickMsg),
}
}
return tea.Subs{
"frame": tea.Every(time.Second/60, newFrameMsg),
}
}
// UPDATE
2020-01-14 13:47:36 -05:00
2020-01-10 16:02:04 -05:00
func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
m, _ := model.(Model)
2020-01-14 13:47:36 -05:00
if !m.Chosen {
return updateChoices(msg, m)
}
return updateChosen(msg, m)
}
func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
2020-01-10 16:02:04 -05:00
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
2020-01-10 16:02:04 -05:00
case "j":
2020-01-10 23:12:25 -05:00
fallthrough
case "down":
m.Choice += 1
if m.Choice > 3 {
m.Choice = 3
2020-01-10 16:02:04 -05:00
}
case "k":
2020-01-10 23:12:25 -05:00
fallthrough
case "up":
m.Choice -= 1
if m.Choice < 0 {
m.Choice = 0
2020-01-10 16:02:04 -05:00
}
2020-01-14 13:47:36 -05:00
case "enter":
m.Chosen = true
return m, nil
2020-01-10 16:02:04 -05:00
case "q":
2020-01-10 23:46:46 -05:00
fallthrough
case "esc":
2020-01-11 11:15:01 -05:00
fallthrough
case "ctrl+c":
2020-01-10 16:02:04 -05:00
return m, tea.Quit
}
2020-01-14 13:47:36 -05:00
case tickMsg:
if m.Ticks == 0 {
return m, tea.Quit
}
m.Ticks -= 1
2020-01-10 16:02:04 -05:00
}
return m, nil
}
2020-01-14 13:47:36 -05:00
func updateChosen(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
2020-01-14 13:47:36 -05:00
case "q":
fallthrough
case "esc":
fallthrough
case "ctrl+c":
2020-01-14 13:47:36 -05:00
return m, tea.Quit
}
case frameMsg:
if !m.Loaded {
m.Frames += 1
m.Progress = ease.OutBounce(float64(m.Frames) / float64(120))
if m.Progress >= 1 {
m.Progress = 1
m.Loaded = true
m.Ticks = 3
}
2020-01-14 13:47:36 -05:00
}
case tickMsg:
if m.Loaded {
if m.Ticks == 0 {
return m, tea.Quit
}
m.Ticks -= 1
}
2020-01-14 13:47:36 -05:00
}
return m, nil
}
// VIEW
2020-01-14 13:47:36 -05:00
func view(model tea.Model) string {
m, _ := model.(Model)
if !m.Chosen {
return choicesView(m)
}
return chosenView(m)
}
2020-01-14 13:47:36 -05:00
const choicesTpl = `What to do today?
%s
Program quits in %d seconds.
2020-01-14 13:47:36 -05:00
(press j/k or up/down to select, enter to choose, and q or esc to quit)`
2020-01-14 13:47:36 -05:00
func choicesView(m Model) string {
c := m.Choice
2020-01-10 16:02:04 -05:00
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),
2020-01-10 16:02:04 -05:00
)
2020-01-14 13:47:36 -05:00
return fmt.Sprintf(choicesTpl, choices, m.Ticks)
}
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."
2020-01-14 13:47:36 -05:00
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)
}
2020-01-15 22:41:45 -05:00
return msg + "\n\n " + label + "\n" + progressbar(80, m.Progress) + "%"
2020-01-10 16:02:04 -05:00
}
func checkbox(label string, checked bool) string {
check := " "
if checked {
check = "x"
}
return fmt.Sprintf("[%s] %s", check, label)
}
2020-01-14 13:47:36 -05:00
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))
}