okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Ayman Bagabasrefactor(examples): move examples to advanced directoryf72cc87

main
2.7 KiB141 linesraw
1package main
2
3import (
4	"context"
5	"fmt"
6	"image/color"
7	"log"
8	"math/rand"
9	"time"
10
11	uv "github.com/charmbracelet/ultraviolet"
12	"github.com/charmbracelet/ultraviolet/screen"
13)
14
15func setupColors(width, height int) [][]color.Color {
16	height = height * 2 // double height for half blocks
17	colors := make([][]color.Color, height)
18
19	for y := 0; y < height; y++ {
20		colors[y] = make([]color.Color, width)
21		randomnessFactor := float64(height-y) / float64(height)
22
23		for x := 0; x < width; x++ {
24			baseValue := randomnessFactor * (float64(height-y) / float64(height))
25			randomOffset := (rand.Float64() * 0.2) - 0.1
26			value := clamp(baseValue+randomOffset, 0, 1)
27
28			// Convert value to grayscale color (0-255)
29			gray := uint8(value * 255)
30			colors[y][x] = color.RGBA{
31				R: gray, G: gray, B: gray, A: 255,
32			}
33		}
34	}
35	return colors
36}
37
38func clamp(value, min, max float64) float64 {
39	if value < min {
40		return min
41	}
42	if value > max {
43		return max
44	}
45	return value
46}
47
48type tickEvent struct{}
49
50func main() {
51	t := uv.DefaultTerminal()
52	scr := t.Screen()
53	scr.EnterAltScreen()
54
55	if err := t.Start(); err != nil {
56		log.Fatalf("failed to start terminal: %v", err)
57	}
58
59	defer t.Stop()
60
61	ctx, cancel := context.WithCancel(context.Background())
62	defer cancel()
63
64	var frameCount int
65	var elapsed time.Duration
66	now := time.Now()
67	fps := 60.0
68	fpsFrameCount := 0
69
70	bounds := scr.Bounds()
71	colors := setupColors(bounds.Dx(), bounds.Dy())
72
73	go t.SendEvent(tickEvent{})
74
75LOOP:
76	for {
77		select {
78		case <-ctx.Done():
79			break LOOP
80		case ev := <-t.Events():
81			switch ev := ev.(type) {
82			case uv.KeyPressEvent:
83				switch ev.String() {
84				case "q", "ctrl+c":
85					cancel()
86					break LOOP
87				}
88
89			case uv.WindowSizeEvent:
90				width, height := ev.Width, ev.Height
91				colors = setupColors(width, height)
92				scr.Resize(width, height)
93			case tickEvent:
94				if len(colors) == 0 {
95					continue
96				}
97
98				frameCount++
99				fpsFrameCount++
100				screen.Clear(scr)
101
102				bounds := scr.Bounds()
103
104				// Title
105				uv.NewStyledString(fmt.Sprintf("\x1b[1mSpace / FPS: %.1f\x1b[m", fps)).
106					Draw(scr, uv.Rect(0, 0, bounds.Dx(), 1))
107
108				// Color display
109				width, height := bounds.Dx(), bounds.Dy()
110				for y := 1; y < height; y++ {
111					for x := 0; x < width; x++ {
112						xi := (x + frameCount) % width
113						fg := colors[y*2][xi]
114						bg := colors[y*2+1][xi]
115						st := uv.Style{
116							Fg: fg,
117							Bg: bg,
118						}
119						scr.SetCell(x, y, &uv.Cell{
120							Content: "▀",
121							Style:   st,
122							Width:   1,
123						})
124					}
125				}
126
127				scr.Render()
128				scr.Flush()
129
130				elapsed = time.Since(now)
131				if elapsed > time.Second && fpsFrameCount > 2 {
132					fps = float64(fpsFrameCount) / elapsed.Seconds()
133					now = time.Now()
134					fpsFrameCount = 0
135				}
136
137				go t.SendEvent(tickEvent{})
138			}
139		}
140	}
141}