Skip to content

Commit

Permalink
feat(examples): add space example to measure fps and performance
Browse files Browse the repository at this point in the history
This is a simple example that uses Lip Gloss and Bubble Tea to render a
moving color gradient.
  • Loading branch information
aymanbagabas committed Jan 8, 2025
1 parent 6231121 commit f882107
Showing 1 changed file with 153 additions and 0 deletions.
153 changes: 153 additions & 0 deletions examples/space/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package main

import (
"fmt"
"image/color"
"math/rand"
"os"
"time"

tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
)

type fps struct {
frameCount int
lastInstant time.Time
fps *float64
}

func (f *fps) tick() {
f.frameCount++
elapsed := time.Since(f.lastInstant)
// Update FPS every second if we have at least 2 frames
if elapsed > time.Second && f.frameCount > 2 {
fps := float64(f.frameCount) / elapsed.Seconds()
f.fps = &fps
f.frameCount = 0
f.lastInstant = time.Now()
}
}

type model struct {
colors [][]color.Color
lastWidth int
lastHeight int
fps fps
frameCount int
width int
height int
}

func initialModel() model {
return model{
fps: fps{
lastInstant: time.Now(),
},
}
}

func (m model) Init() (tea.Model, tea.Cmd) {
return m, tea.Batch(
tea.EnterAltScreen,
tickCmd(),
)
}

func tickCmd() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return tickMsg{}
})
}

type tickMsg struct{}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}

case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
if m.width != m.lastWidth || m.height != m.lastHeight {
m.setupColors()
m.lastWidth = m.width
m.lastHeight = m.height
}

case tickMsg:
m.frameCount++
m.fps.tick()
return m, tickCmd()
}

return m, nil
}

func (m *model) setupColors() {
height := m.height * 2 // double height for half blocks
m.colors = make([][]color.Color, height)

for y := 0; y < height; y++ {
m.colors[y] = make([]color.Color, m.width)
randomnessFactor := float64(height-y) / float64(height)

for x := 0; x < m.width; x++ {
baseValue := randomnessFactor * (float64(height-y) / float64(height))
randomOffset := (rand.Float64() * 0.2) - 0.1
value := clamp(baseValue+randomOffset, 0, 1)

// Convert value to grayscale color (0-255)
gray := uint8(value * 255)
m.colors[y][x] = lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", gray, gray, gray))
}
}
}

func clamp(value, min, max float64) float64 {
if value < min {
return min
}
if value > max {
return max
}
return value
}

func (m model) View() string {
// Title and FPS display
title := lipgloss.NewStyle().Bold(true).Render("Space ")
fpsText := ""
if m.fps.fps != nil {
fpsText = fmt.Sprintf("%.1f fps", *m.fps.fps)
}
header := lipgloss.JoinHorizontal(lipgloss.Center, title, fpsText)

// Color display
var s string
for y := 0; y < m.height; y++ {
for x := 0; x < m.width; x++ {
xi := (x + m.frameCount) % m.width
fg := m.colors[y*2][xi]
bg := m.colors[y*2+1][xi]
s += lipgloss.NewStyle().Foreground(fg).Background(bg).Render("▀")
}
s += "\n"
}

return lipgloss.JoinVertical(lipgloss.Left, header, s)
}

func main() {
p := tea.NewProgram(initialModel(), tea.WithAltScreen())

_, err := p.Run()
if err != nil {
fmt.Printf("Error running program: %v", err)
os.Exit(1)
}
}

0 comments on commit f882107

Please sign in to comment.