2020-01-10 16:02:04 -05:00
|
|
|
|
package main
|
|
|
|
|
|
2020-01-17 20:46:34 -05:00
|
|
|
|
// 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"
|
2020-01-13 17:10:23 -05:00
|
|
|
|
"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() {
|
2020-01-13 19:07:04 -05:00
|
|
|
|
p := tea.NewProgram(
|
2020-01-18 22:18:19 -05:00
|
|
|
|
initialize,
|
2020-01-13 19:07:04 -05:00
|
|
|
|
update,
|
|
|
|
|
view,
|
2020-01-25 21:27:43 -05:00
|
|
|
|
subscriptions,
|
2020-01-13 19:07:04 -05:00
|
|
|
|
)
|
2020-01-11 10:45:15 -05:00
|
|
|
|
if err := p.Start(); err != nil {
|
|
|
|
|
fmt.Println("could not start program:", err)
|
|
|
|
|
}
|
2020-01-10 16:02:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 14:26:06 -04: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
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-18 22:18:19 -05:00
|
|
|
|
// 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
|
|
|
|
|
|
2020-01-25 21:27:43 -05:00
|
|
|
|
func subscriptions(model tea.Model) tea.Subs {
|
|
|
|
|
m, _ := model.(Model)
|
|
|
|
|
if !m.Chosen || m.Loaded {
|
|
|
|
|
return tea.Subs{
|
2020-05-05 14:26:06 -04:00
|
|
|
|
"tick": tea.Every(time.Second, newTickMsg),
|
2020-01-25 21:27:43 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return tea.Subs{
|
2020-05-05 14:26:06 -04:00
|
|
|
|
"frame": tea.Every(time.Second/60, newFrameMsg),
|
2020-01-25 21:27:43 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 14:26:06 -04:00
|
|
|
|
// 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) {
|
2020-01-13 17:10:23 -05:00
|
|
|
|
|
2020-01-17 20:46:34 -05:00
|
|
|
|
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":
|
2020-01-13 17:10:23 -05:00
|
|
|
|
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":
|
2020-01-13 17:10:23 -05:00
|
|
|
|
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
|
2020-01-26 16:46:30 -05:00
|
|
|
|
case "ctrl+c":
|
2020-01-10 16:02:04 -05:00
|
|
|
|
return m, tea.Quit
|
|
|
|
|
}
|
2020-01-13 17:10:23 -05:00
|
|
|
|
|
2020-01-14 13:47:36 -05:00
|
|
|
|
case tickMsg:
|
2020-01-13 19:07:04 -05:00
|
|
|
|
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) {
|
|
|
|
|
|
2020-01-17 20:46:34 -05:00
|
|
|
|
case tea.KeyMsg:
|
|
|
|
|
switch msg.String() {
|
2020-01-14 13:47:36 -05:00
|
|
|
|
case "q":
|
|
|
|
|
fallthrough
|
|
|
|
|
case "esc":
|
|
|
|
|
fallthrough
|
2020-01-26 16:46:30 -05:00
|
|
|
|
case "ctrl+c":
|
2020-01-14 13:47:36 -05:00
|
|
|
|
return m, tea.Quit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case frameMsg:
|
2020-01-14 16:24:01 -05:00
|
|
|
|
if !m.Loaded {
|
|
|
|
|
m.Frames += 1
|
2020-01-15 16:06:14 -05:00
|
|
|
|
m.Progress = ease.OutBounce(float64(m.Frames) / float64(120))
|
2020-01-14 16:24:01 -05:00
|
|
|
|
if m.Progress >= 1 {
|
|
|
|
|
m.Progress = 1
|
|
|
|
|
m.Loaded = true
|
|
|
|
|
m.Ticks = 3
|
|
|
|
|
}
|
2020-01-14 13:47:36 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 16:24:01 -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
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 14:26:06 -04:00
|
|
|
|
// 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-13 17:10:23 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 13:47:36 -05:00
|
|
|
|
const choicesTpl = `What to do today?
|
2020-01-13 19:07:04 -05:00
|
|
|
|
|
|
|
|
|
%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-13 19:07:04 -05:00
|
|
|
|
|
2020-01-14 13:47:36 -05:00
|
|
|
|
func choicesView(m Model) string {
|
2020-01-13 17:10:23 -05:00
|
|
|
|
c := m.Choice
|
2020-01-10 16:02:04 -05:00
|
|
|
|
|
|
|
|
|
choices := fmt.Sprintf(
|
|
|
|
|
"%s\n%s\n%s\n%s",
|
2020-01-13 17:10:23 -05:00
|
|
|
|
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:
|
2020-01-14 16:24:01 -05:00
|
|
|
|
msg = "Reading time?\n\nOkay, cool, then we’ll need a library. Yes, an actual library."
|
2020-01-14 13:47:36 -05:00
|
|
|
|
default:
|
|
|
|
|
msg = "It’s always good to see friends.\n\nFetching social-skills and conversationutils..."
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 16:24:01 -05:00
|
|
|
|
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))
|
|
|
|
|
}
|