confetty/confetti/confetti.go

170 lines
3.2 KiB
Go
Raw Normal View History

2021-08-07 23:57:00 +00:00
package confetti
2021-08-07 01:24:56 +00:00
import (
"fmt"
"math/rand"
"strings"
"time"
2021-08-07 18:46:09 +00:00
"github.com/maaslalani/confetty/array"
"github.com/maaslalani/confetty/physics"
2021-08-07 02:00:08 +00:00
2021-08-07 01:24:56 +00:00
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"golang.org/x/term"
)
2021-08-07 03:43:02 +00:00
const (
framesPerSecond = 60.0
numParticles = 75
)
2021-08-07 01:24:56 +00:00
2021-08-07 02:08:18 +00:00
var (
colors = []string{"#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"}
2021-08-07 18:43:40 +00:00
characters = []string{"█", "▓", "▒", "░", "▄", "▀"}
// characters = []string{"▄", "▀"}
2021-08-07 02:08:18 +00:00
)
2021-08-07 01:24:56 +00:00
type frameMsg time.Time
func animate() tea.Cmd {
2021-08-07 03:43:02 +00:00
return tea.Tick(time.Second/framesPerSecond, func(t time.Time) tea.Msg {
2021-08-07 01:24:56 +00:00
return frameMsg(t)
})
}
2021-08-07 02:25:00 +00:00
func wait(d time.Duration) tea.Cmd {
2021-08-07 01:24:56 +00:00
return func() tea.Msg {
2021-08-07 02:25:00 +00:00
time.Sleep(d)
2021-08-07 01:24:56 +00:00
return nil
}
}
2021-08-07 03:43:02 +00:00
// Confetti model
2021-08-07 01:24:56 +00:00
type model struct {
particles []*Particle
viewport viewport.Model
}
type Particle struct {
2021-08-07 02:00:08 +00:00
char string
physics *physics.Physics
2021-08-07 01:24:56 +00:00
}
func InitialModel() model {
particles := []*Particle{}
2021-08-07 03:43:02 +00:00
width, _, err := term.GetSize(0)
2021-08-07 01:24:56 +00:00
if err != nil {
panic(err)
}
2021-08-07 03:43:02 +00:00
for i := 0; i < numParticles; i++ {
2021-08-07 01:24:56 +00:00
x := float64(width / 2)
2021-08-07 03:43:02 +00:00
y := float64(-1)
2021-08-07 01:24:56 +00:00
p := &Particle{
2021-08-07 02:00:08 +00:00
physics: physics.New(
2021-08-07 21:45:59 +00:00
physics.Point{X: x + (float64(width/4) * (rand.Float64() - 0.5)), Y: y},
2021-08-07 03:43:02 +00:00
physics.Vector{X: (rand.Float64() - 0.5) * 100, Y: rand.Float64() * 50},
2021-08-07 02:00:08 +00:00
physics.Vector(physics.Gravity),
2021-08-07 03:43:02 +00:00
framesPerSecond,
2021-08-07 02:00:08 +00:00
),
2021-08-07 02:08:18 +00:00
char: lipgloss.NewStyle().
Foreground(lipgloss.Color(array.Sample(colors))).
Render(array.Sample(characters)),
2021-08-07 01:24:56 +00:00
}
2021-08-07 02:08:18 +00:00
2021-08-07 01:24:56 +00:00
particles = append(particles, p)
}
return model{particles: particles}
}
2021-08-07 03:43:02 +00:00
// Init initializes the confetti after a small delay
2021-08-07 01:24:56 +00:00
func (m model) Init() tea.Cmd {
2021-08-07 03:43:02 +00:00
return tea.Sequentially(wait(time.Second/2), animate())
2021-08-07 01:24:56 +00:00
}
2021-08-07 03:43:02 +00:00
// Update updates the model every frame, it handles the animation loop and
// updates the particle physics every frame
2021-08-07 01:24:56 +00:00
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return m, tea.Quit
2021-08-07 03:43:02 +00:00
// frame animation
2021-08-07 01:24:56 +00:00
case frameMsg:
2021-08-07 20:13:35 +00:00
particlesVisible := numParticles
2021-08-07 01:24:56 +00:00
for _, p := range m.particles {
2021-08-07 02:00:08 +00:00
p.physics.Update()
2021-08-07 20:13:35 +00:00
y := p.physics.PosY()
x := p.physics.PosX()
// Particle is out of view
if y >= m.viewport.Height-1 || x < 0 || x >= m.viewport.Width-1 {
particlesVisible -= 1
}
}
if particlesVisible <= 0 {
for _, p := range m.particles {
p.physics.Reset()
}
2021-08-07 01:24:56 +00:00
}
2021-08-07 20:13:35 +00:00
2021-08-07 01:24:56 +00:00
return m, animate()
2021-08-07 03:43:02 +00:00
2021-08-07 01:24:56 +00:00
case tea.WindowSizeMsg:
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height
return m, nil
2021-08-07 03:43:02 +00:00
2021-08-07 01:24:56 +00:00
default:
return m, nil
}
}
2021-08-07 03:43:02 +00:00
// View displays all the particles on the screen
2021-08-07 01:24:56 +00:00
func (m model) View() string {
2021-08-07 20:13:35 +00:00
height := m.viewport.Height
width := m.viewport.Width
if height <= 0 || width <= 0 {
2021-08-07 01:24:56 +00:00
return ""
}
2021-08-07 20:13:35 +00:00
2021-08-07 01:24:56 +00:00
var out strings.Builder
2021-08-07 20:13:35 +00:00
2021-08-07 01:24:56 +00:00
grid := make([][]string, m.viewport.Height)
for i := range grid {
grid[i] = make([]string, m.viewport.Width)
}
2021-08-07 03:43:02 +00:00
2021-08-07 01:24:56 +00:00
for _, p := range m.particles {
2021-08-07 02:00:08 +00:00
y := p.physics.PosY()
x := p.physics.PosX()
2021-08-07 03:43:02 +00:00
2021-08-07 20:13:35 +00:00
if y < 0 || x < 0 || y >= height-1 || x >= width-1 {
2021-08-07 01:24:56 +00:00
continue
}
2021-08-07 03:43:02 +00:00
2021-08-07 01:24:56 +00:00
grid[y][x] = p.char
}
2021-08-07 03:43:02 +00:00
// Print out grid
2021-08-07 01:24:56 +00:00
for i := range grid {
for _, col := range grid[i] {
if col == "" {
fmt.Fprint(&out, " ")
} else {
fmt.Fprint(&out, col)
}
}
fmt.Fprint(&out, "\n")
}
2021-08-07 03:43:02 +00:00
2021-08-07 01:24:56 +00:00
return out.String()
}