├── .gitignore ├── cli ├── .gitignore ├── demo.js ├── runtime │ ├── javascript.go │ └── runtime.go ├── go.mod ├── embed.go └── main.go ├── docs ├── screenshot.png └── the-great-wave-kanagawa.png ├── examples ├── glitch │ ├── mona-lisa.jpg │ ├── glitch.js │ ├── glitch.go │ ├── go.mod │ └── go.sum ├── image │ ├── the-great-wave-kanagawa.jpg │ ├── image.js │ ├── image.go │ ├── go.mod │ └── go.sum ├── bezier │ ├── bezier.js │ ├── bezier.go │ ├── go.mod │ └── go.sum ├── translate │ ├── translate.js │ ├── translate.go │ └── go.mod ├── ellipse-rotation │ ├── ellipse-rotation.js │ ├── ellipse-rotation.go │ └── go.mod ├── mouse │ ├── mouse.js │ ├── mouse.go │ └── go.mod ├── colors │ ├── colors.js │ ├── colors.go │ ├── go.mod │ └── go.sum ├── triangle │ ├── triangle.js │ ├── triangle.go │ └── go.mod ├── square │ ├── square.js │ ├── square.go │ └── go.mod ├── quad │ ├── quad.js │ ├── quad.go │ └── go.mod ├── scale │ ├── scale.js │ ├── scale.go │ └── go.mod ├── line │ ├── line.js │ ├── line.go │ ├── go.mod │ └── go.sum ├── grid │ ├── grid.js │ ├── grid.go │ ├── go.mod │ └── go.sum ├── noiseloop1d │ ├── noiseloop1d.js │ ├── noiseloop1d.go │ └── go.mod ├── circle │ ├── circle.js │ ├── circle.go │ ├── go.mod │ └── go.sum ├── noiseloop2d │ ├── noiseloop2d.js │ ├── noiseloop2d.go │ └── go.mod ├── gif-export │ ├── gif-export.js │ ├── gif-export.go │ └── go.mod ├── draw │ ├── draw.js │ ├── draw.go │ ├── go.mod │ └── go.sum ├── noise │ ├── noise.js │ ├── noise.go │ └── go.mod ├── noiseloop-exportmp4 │ ├── noiseloop-exportmp4.js │ ├── noiseloop-exportmp4.go │ └── go.mod ├── world │ ├── world.js │ ├── world.go │ └── go.mod ├── lissajous │ ├── lissajous.js │ ├── lissajous.go │ └── go.mod ├── spiral │ ├── spiral.js │ ├── spiral.go │ └── go.mod └── strokefill │ ├── go.mod │ ├── strokefill.js │ └── strokefill.go ├── signals_windows.go ├── signals_unix.go ├── math.go ├── transform.go ├── .github ├── dependabot.yml └── workflows │ ├── checks.yml │ └── build.yml ├── style.go ├── random.go ├── event.go ├── curve.go ├── canvas_test.go ├── .golangci.yml ├── ffmpeg.go ├── ansi.go ├── debug.go ├── frame.go ├── util.go ├── cell.go ├── LICENSE ├── Makefile ├── input.go ├── go.mod ├── structure.go ├── stroke.go ├── runal.go ├── image.go ├── noise.go ├── capture.go ├── run.go ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /cli/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !demo.js 3 | *.log 4 | *.swp 5 | *.png 6 | *.gif 7 | *.mp4 8 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emprcl/runal/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /examples/glitch/mona-lisa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emprcl/runal/HEAD/examples/glitch/mona-lisa.jpg -------------------------------------------------------------------------------- /docs/the-great-wave-kanagawa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emprcl/runal/HEAD/docs/the-great-wave-kanagawa.png -------------------------------------------------------------------------------- /examples/image/the-great-wave-kanagawa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emprcl/runal/HEAD/examples/image/the-great-wave-kanagawa.jpg -------------------------------------------------------------------------------- /examples/bezier/bezier.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.cellModeCustom(" "); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | c.stroke("0", "#ffffff", "#000000"); 8 | c.bezier(10, 10, 20, 0, 30, 20, 40, 10); 9 | } 10 | -------------------------------------------------------------------------------- /signals_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package runal 4 | 5 | import "os" 6 | 7 | func listenForResize() chan os.Signal { 8 | // SIGWINCH is not implemented on Windows. 9 | return make(chan os.Signal, 1) 10 | } 11 | -------------------------------------------------------------------------------- /examples/translate/translate.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.noLoop(); 3 | c.cellModeCustom(" "); 4 | } 5 | 6 | function draw(c) { 7 | c.clear(); 8 | c.circle(0, 0, 5); 9 | c.translate(c.width / 2, c.height / 2); 10 | c.circle(0, 0, 5); 11 | } 12 | -------------------------------------------------------------------------------- /signals_unix.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || linux 2 | 3 | package runal 4 | 5 | import ( 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | func listenForResize() chan os.Signal { 12 | resize := make(chan os.Signal, 1) 13 | signal.Notify(resize, syscall.SIGWINCH) 14 | return resize 15 | } 16 | -------------------------------------------------------------------------------- /examples/ellipse-rotation/ellipse-rotation.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.cellModeCustom(" "); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | c.fill("ellipse", "#ffffff", "#000000"); 8 | c.translate(c.width / 2, c.height / 2); 9 | c.rotate(c.framecount * 0.008); 10 | c.ellipse(0, 0, 15, 5); 11 | } 12 | -------------------------------------------------------------------------------- /examples/image/image.js: -------------------------------------------------------------------------------- 1 | let img; 2 | 3 | function setup(c) { 4 | img = c.loadImage("the-great-wave-kanagawa.jpg"); 5 | c.noLoop(); 6 | } 7 | 8 | function draw(c) { 9 | c.clear(); 10 | c.image(img, 0, 0, c.width, c.height); 11 | let fullCanvas = c.get(0, 0, c.width, c.height); 12 | c.set(c.width / 2, 0, fullCanvas); 13 | } 14 | -------------------------------------------------------------------------------- /examples/mouse/mouse.js: -------------------------------------------------------------------------------- 1 | function setup(c) {} 2 | 3 | function draw(c) { 4 | c.clear(); 5 | c.circle(c.mouseX, c.mouseY, 5); 6 | } 7 | 8 | function onKey(c, event) { 9 | if (event.key == "space") { 10 | c.backgroundBg(c.random(0, 255)); 11 | } 12 | } 13 | 14 | function onMouseClick(c, event) { 15 | c.backgroundBg(c.random(0, 255)); 16 | } 17 | -------------------------------------------------------------------------------- /examples/colors/colors.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.noLoop() 3 | } 4 | 5 | function draw(c) { 6 | for (i = 0; i < 256; i++) { 7 | c.push() 8 | c.stroke(" ", "0", i) 9 | c.translate((i % 16) * 10, Math.floor(i / 16) * 3) 10 | c.line(2, 1, 6, 1) 11 | c.line(2, 2, 6, 2) 12 | c.stroke(" ", "15", "0") 13 | c.text(i, 8, 1) 14 | c.pop() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/triangle/triangle.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.cellModeCustom(" "); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | c.stroke(".", "#ffffff", "#000000"); 8 | c.fill("triangle", "#ffffff", "#000000"); 9 | c.translate(c.width / 2, c.height / 2); 10 | c.rotate(c.framecount * 0.008); 11 | c.scale(1); 12 | c.triangle(5, 5, 15, 15, 2, 15); 13 | } 14 | -------------------------------------------------------------------------------- /examples/square/square.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.cellModeCustom(" "); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | console.log(c.framecount); 8 | c.stroke("BORDER", "#ffffff", "#555555"); 9 | c.fill("square", "#ffffff", "#000000"); 10 | c.translate(c.width / 2, c.height / 2); 11 | c.rotate(c.framecount * 0.08); 12 | c.scale(1); 13 | c.square(0, 0, 10); 14 | } 15 | -------------------------------------------------------------------------------- /examples/bezier/bezier.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | func setup(c *runal.Canvas) { 14 | c.CellModeCustom(" ") 15 | } 16 | 17 | func draw(c *runal.Canvas) { 18 | c.Clear() 19 | c.Stroke("0", "#ffffff", "#000000") 20 | c.Bezier(10, 10, 20, 0, 30, 20, 40, 10) 21 | } 22 | -------------------------------------------------------------------------------- /examples/quad/quad.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.cellModeCustom(" "); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | c.stroke(".", "#ffffff", "#555555"); 8 | c.fill("quad", "#ffffff", "#000000"); 9 | c.quad( 10 | c.map(Math.sin(c.framecount * 0.1), -1, 1, 1, 35), 11 | 1, 12 | c.map(Math.cos(c.framecount * 0.1), -1, 1, 1, 35), 13 | 3, 14 | 16, 15 | 12, 16 | 2, 17 | 18, 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /examples/scale/scale.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | //c.noLoop(); 3 | c.cellModeCustom(" "); 4 | } 5 | 6 | function draw(c) { 7 | c.clear(); 8 | c.stroke(".", "#ffffff", "#ffffff"); 9 | c.fill(".", "#ffffff", "#000000"); 10 | c.translate(c.width / 2, c.height / 2); 11 | c.scale(c.map(Math.sin(c.framecount * 0.1), -1, 1, 1, 4)); 12 | c.rotate(c.framecount * 0.008); 13 | c.circle(0, 0, 5); 14 | c.circle(10, 10, 5); 15 | } 16 | -------------------------------------------------------------------------------- /examples/translate/translate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | func setup(c *runal.Canvas) { 14 | c.NoLoop() 15 | c.CellModeCustom(" ") 16 | } 17 | 18 | func draw(c *runal.Canvas) { 19 | c.Clear() 20 | c.Circle(0, 0, 5) 21 | c.Translate(c.Width/2, c.Height/2) 22 | c.Circle(0, 0, 5) 23 | } 24 | -------------------------------------------------------------------------------- /examples/line/line.js: -------------------------------------------------------------------------------- 1 | function setup(c) {} 2 | 3 | function draw(c) { 4 | c.clear(); 5 | let y1 = 6 | (Math.sin((c.framecount * 0.2 + 1000) * 0.2) / 2 + 0.5) * c.height * 0.8; 7 | let x1 = 8 | (Math.cos((c.framecount * 0.2 + 1000) * 0.2) / 2 + 0.5) * c.width * 0.8; 9 | let y2 = (Math.sin(c.framecount * 0.1) / 2 + 0.5) * c.height * 0.8; 10 | let x2 = (Math.cos(c.framecount * 0.1) / 2 + 0.5) * c.width * 0.8; 11 | c.line(x1, y1, x2, y2); 12 | } 13 | -------------------------------------------------------------------------------- /examples/grid/grid.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.noLoop(); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | for (let i = 0; i < c.width; i++) { 8 | for (let j = 0; j < c.height; j++) { 9 | if (Math.random() < 0.8) { 10 | c.text(".", i, j); 11 | } 12 | } 13 | } 14 | } 15 | 16 | function onKey(c, e) { 17 | if (e.key == "space") { 18 | c.redraw(); 19 | } 20 | } 21 | 22 | function onMouseClick(c, e) { 23 | c.redraw(); 24 | } 25 | -------------------------------------------------------------------------------- /examples/ellipse-rotation/ellipse-rotation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | func setup(c *runal.Canvas) { 14 | c.CellModeCustom(" ") 15 | } 16 | 17 | func draw(c *runal.Canvas) { 18 | c.Clear() 19 | c.Fill("ellipse", "#ffffff", "#000000") 20 | c.Translate(c.Width/2, c.Height/2) 21 | c.Rotate(float64(c.Framecount) * 0.008) 22 | c.Ellipse(0, 0, 15, 5) 23 | } 24 | -------------------------------------------------------------------------------- /math.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import "math" 4 | 5 | // Dist returns the Euclidean distance between two points. 6 | func (c *Canvas) Dist(x1, y1, x2, y2 int) float64 { 7 | dx := x2 - x1 8 | dy := y2 - y1 9 | return math.Sqrt(float64(dx*dx + dy*dy)) 10 | } 11 | 12 | // Map linearly maps a value from one range to another. 13 | func (c *Canvas) Map(value, inputStart, inputEnd, outputStart, outputEnd float64) float64 { 14 | return outputStart + ((outputEnd-outputStart)/(inputEnd-inputStart))*(value-inputStart) 15 | } 16 | -------------------------------------------------------------------------------- /transform.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | // Translate offsets the drawing context by (x, y). 4 | func (c *Canvas) Translate(x, y int) { 5 | c.originX = c.originX + x 6 | c.originY = c.originY + y 7 | } 8 | 9 | // Rotate rotates the drawing context by the given angle in radians. 10 | func (c *Canvas) Rotate(angle float64) { 11 | c.rotationAngle = c.rotationAngle + angle 12 | } 13 | 14 | // Scale scales the drawing context by the given factor. 15 | func (c *Canvas) Scale(scale float64) { 16 | c.scale = c.scale * scale 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /examples/square/square.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | func setup(c *runal.Canvas) { 14 | c.CellModeCustom(" ") 15 | } 16 | 17 | func draw(c *runal.Canvas) { 18 | c.Clear() 19 | c.Stroke("BORDER", "#ffffff", "#555555") 20 | c.Fill("square", "#ffffff", "#000000") 21 | c.Translate(c.Width/2, c.Height/2) 22 | c.Rotate(float64(c.Framecount) * 0.08) 23 | c.Scale(1) 24 | c.Square(0, 0, 10) 25 | } 26 | -------------------------------------------------------------------------------- /style.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | type style struct { 6 | foreground ansi.Color 7 | background ansi.Color 8 | } 9 | 10 | func (s style) equals(s2 style) bool { 11 | return s.background == s2.background && s.foreground == s2.foreground 12 | } 13 | 14 | func (s style) render(str string) string { 15 | return ansi.NewStyle(). 16 | BackgroundColor(s.background). 17 | ForegroundColor(s.foreground). 18 | String() + str 19 | } 20 | 21 | func resetStyle() string { 22 | return "\x1b[0m" 23 | } 24 | -------------------------------------------------------------------------------- /examples/image/image.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | var img runal.Image 14 | 15 | func setup(c *runal.Canvas) { 16 | img = c.LoadImage("the-great-wave-kanagawa.jpg") 17 | c.NoLoop() 18 | } 19 | 20 | func draw(c *runal.Canvas) { 21 | c.Image(img, 0, 0, c.Width, c.Height) 22 | 23 | fullCanvas := c.Get(0, 0, c.Width, c.Height) 24 | 25 | c.Image(fullCanvas, c.Width/2, 0, c.Width/2, c.Height) 26 | } 27 | -------------------------------------------------------------------------------- /examples/noiseloop1d/noiseloop1d.js: -------------------------------------------------------------------------------- 1 | let duration = 1; 2 | let scale = 1; 3 | 4 | function setup(c) {} 5 | 6 | function draw(c) { 7 | c.clear(); 8 | c.strokeText("THIS IS A 1D NOISE LOOP EXAMPLE"); 9 | let theta = c.loopAngle(duration); 10 | 11 | for (let x = 0; x < c.width; x++) { 12 | let noise = c.noiseLoop1D(theta, 0.1, x * scale); 13 | let y = c.map(noise, 0, 1, 0, c.height); 14 | c.point(x, y); 15 | } 16 | } 17 | 18 | function onKey(c, e) { 19 | if (e.key == "space") { 20 | c.noiseSeed(Date.now()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/triangle/triangle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | func setup(c *runal.Canvas) { 14 | c.CellModeCustom(" ") 15 | } 16 | 17 | func draw(c *runal.Canvas) { 18 | c.Clear() 19 | c.Stroke(".", "#ffffff", "#000000") 20 | c.Fill("triangle", "#ffffff", "#000000") 21 | c.Translate(c.Width/2, c.Height/2) 22 | c.Rotate(float64(c.Framecount) * 0.008) 23 | c.Scale(1) 24 | c.Triangle(5, 5, 15, 15, 2, 15) 25 | } 26 | -------------------------------------------------------------------------------- /random.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func newRandom() *rand.Rand { 9 | return rand.New(rand.NewSource(time.Now().UnixNano())) 10 | } 11 | 12 | // Random returns a random float between minimum and maximum. 13 | func (c *Canvas) Random(minimum, maximum int) float64 { 14 | return c.Map(c.random.Float64(), 0, 1, float64(minimum), float64(maximum)) 15 | } 16 | 17 | // RandomSeed sets the random number generator seed. 18 | func (c *Canvas) RandomSeed(seed int64) { 19 | c.random = rand.New(rand.NewSource(seed)) 20 | } 21 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | type event struct { 4 | name string 5 | value int 6 | } 7 | 8 | func newFPSEvent(fps int) event { 9 | return event{ 10 | name: "fps", 11 | value: fps, 12 | } 13 | } 14 | 15 | func newStartEvent() event { 16 | return event{ 17 | name: "start", 18 | } 19 | } 20 | 21 | func newStopEvent() event { 22 | return event{ 23 | name: "stop", 24 | } 25 | } 26 | 27 | func newRenderEvent() event { 28 | return event{ 29 | name: "render", 30 | } 31 | } 32 | 33 | func newExitEvent() event { 34 | return event{ 35 | name: "exit", 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/circle/circle.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.cellModeCustom("."); 3 | } 4 | 5 | function draw(c) { 6 | c.clear(); 7 | 8 | let radius1 = ((Math.sin(c.framecount * 0.1) * 0.5 + 0.5) * c.width) / 2; 9 | let radius2 = ((Math.sin(c.framecount * 0.2) * 0.5 + 0.5) * c.width) / 3; 10 | 11 | c.stroke("COUCOU", "#ffffff", "#000000"); 12 | c.fill("i", "#ffffff", "#000000"); 13 | c.circle(c.width / 2, c.height / 2, radius1); 14 | 15 | c.stroke("C", "#ffffff", "#000000"); 16 | c.fill("vvvv", "#ffffff", "#000000"); 17 | c.circle(c.width / 2, c.height / 2, radius2); 18 | } 19 | -------------------------------------------------------------------------------- /examples/noiseloop2d/noiseloop2d.js: -------------------------------------------------------------------------------- 1 | let duration = 3; 2 | let scale = 0.3; 3 | 4 | function setup(c) {} 5 | 6 | function draw(c) { 7 | c.clear(); 8 | let theta = c.loopAngle(duration); 9 | 10 | for (let x = 0; x < c.width; x++) { 11 | for (let y = 0; y < c.height; y++) { 12 | let noise = c.noiseLoop2D(theta, 1, x * scale, y * scale); 13 | let color = c.map(noise, 0, 1, 232, 255); 14 | c.stroke("§", color, 0); 15 | c.point(x, y); 16 | } 17 | } 18 | } 19 | 20 | function onKey(c, e) { 21 | if (e.key == "space") { 22 | c.noiseSeed(Date.now()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /curve.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import "math" 4 | 5 | // Bezier draws a Bézier curve using four control points. 6 | func (c *Canvas) Bezier(x1, y1, x2, y2, x3, y3, x4, y4 int) { 7 | steps := 50 8 | 9 | for i := 0; i <= steps; i++ { 10 | t := float64(i) / float64(steps) 11 | u := 1 - t 12 | 13 | x := u*u*u*float64(x1) + 14 | 3*u*u*t*float64(x2) + 15 | 3*u*t*t*float64(x3) + 16 | t*t*t*float64(x4) 17 | 18 | y := u*u*u*float64(y1) + 19 | 3*u*u*t*float64(y2) + 20 | 3*u*t*t*float64(y3) + 21 | t*t*t*float64(y4) 22 | 23 | c.Point(int(math.Round(x)), int(math.Round(y))) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /canvas_test.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var benchmarks = []struct { 9 | size int 10 | }{ 11 | {size: 20}, 12 | {size: 100}, 13 | {size: 200}, 14 | {size: 300}, 15 | {size: 400}, 16 | {size: 500}, 17 | } 18 | 19 | func BenchmarkCanvasRender(b *testing.B) { 20 | for _, v := range benchmarks { 21 | b.Run(fmt.Sprintf("canvas_size_%dx%d", v.size, v.size), func(b *testing.B) { 22 | canvas := mockCanvas(v.size, v.size) 23 | canvas.Rect(5, 5, 5, 5) 24 | canvas.Clear() 25 | for i := 0; i < b.N; i++ { 26 | canvas.render() 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: checks 2 | on: 3 | push: 4 | branches: ["main"] 5 | tags: ["v*"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | permissions: 10 | contents: read 11 | pull-requests: read 12 | 13 | jobs: 14 | golangci: 15 | name: checks 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.24 21 | - uses: actions/checkout@v4 22 | - name: golangci-lint 23 | uses: golangci/golangci-lint-action@v8 24 | with: 25 | args: -c .golangci.yml 26 | only-new-issues: true 27 | -------------------------------------------------------------------------------- /examples/gif-export/gif-export.js: -------------------------------------------------------------------------------- 1 | let duration = 5; 2 | let margin = 3; 3 | 4 | function setup(c) { 5 | c.size(40, 21); 6 | c.backgroundBg("197"); 7 | c.saveCanvasToGIF("canvas.gif", duration); 8 | } 9 | 10 | function draw(c) { 11 | c.clear(); 12 | c.stroke("RUNAL", "255", "197"); 13 | let theta = c.loopAngle(duration); 14 | 15 | for (let y = margin; y < c.height - margin; y++) { 16 | let x = c.map(Math.sin(theta + y), -1, 1, margin, c.width - margin); 17 | 18 | c.point(x, y); 19 | } 20 | } 21 | 22 | function onKey(c, e) { 23 | if (e.key == "space") { 24 | c.noiseSeed(Date.now()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/quad/quad.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | func main() { 11 | runal.Run(context.Background(), setup, draw) 12 | } 13 | 14 | func setup(c *runal.Canvas) { 15 | c.CellModeCustom(" ") 16 | } 17 | 18 | func draw(c *runal.Canvas) { 19 | c.Clear() 20 | c.Stroke(".", "#ffffff", "#555555") 21 | c.Fill("quad", "#ffffff", "#000000") 22 | c.Quad( 23 | int(c.Map(math.Sin(float64(c.Framecount)*0.1), -1, 1, 1, 35)), 24 | 1, 25 | int(c.Map(math.Cos(float64(c.Framecount)*0.1), -1, 1, 1, 35)), 26 | 3, 27 | 16, 28 | 12, 29 | 2, 30 | 18, 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /examples/scale/scale.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | func main() { 11 | runal.Run(context.Background(), setup, draw) 12 | } 13 | 14 | func setup(c *runal.Canvas) { 15 | //c.NoLoop(); 16 | c.CellModeCustom(" ") 17 | } 18 | 19 | func draw(c *runal.Canvas) { 20 | c.Clear() 21 | c.Stroke(".", "#ffffff", "#ffffff") 22 | c.Fill(".", "#ffffff", "#000000") 23 | c.Translate(c.Width/2, c.Height/2) 24 | c.Scale(c.Map(math.Sin(float64(c.Framecount)*0.1), -1, 1, 1, 4)) 25 | c.Rotate(float64(c.Framecount) * 0.008) 26 | c.Circle(0, 0, 5) 27 | c.Circle(10, 10, 5) 28 | } 29 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | go: "1.23" 4 | linters: 5 | enable: 6 | - thelper 7 | - tparallel 8 | - unconvert 9 | - unparam 10 | exclusions: 11 | generated: lax 12 | presets: 13 | - comments 14 | - common-false-positives 15 | - legacy 16 | - std-error-handling 17 | paths: 18 | - third_party$ 19 | - builtin$ 20 | - examples$ 21 | formatters: 22 | enable: 23 | - gofumpt 24 | settings: 25 | gofumpt: 26 | module-path: runal 27 | exclusions: 28 | generated: lax 29 | paths: 30 | - third_party$ 31 | - builtin$ 32 | - examples$ 33 | -------------------------------------------------------------------------------- /examples/colors/colors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "strconv" 8 | 9 | "github.com/emprcl/runal" 10 | ) 11 | 12 | func main() { 13 | runal.Run(context.Background(), setup, draw) 14 | } 15 | 16 | func setup(c *runal.Canvas) { 17 | c.NoLoop() 18 | } 19 | 20 | func draw(c *runal.Canvas) { 21 | for i := range 256 { 22 | c.Push() 23 | c.Stroke(" ", "0", strconv.Itoa(i)) 24 | c.Translate((i%16)*10, int(math.Floor(float64(i/16)))*3) 25 | c.Line(2, 1, 6, 1) 26 | c.Line(2, 2, 6, 2) 27 | c.Stroke(" ", "15", "0") 28 | text := fmt.Sprintf("%d", i) 29 | c.Text(text, 8, 1) 30 | c.Pop() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/draw/draw.js: -------------------------------------------------------------------------------- 1 | let x1 = 0, 2 | y1 = 0, 3 | x2 = 0, 4 | y2 = 0; 5 | 6 | function setup(c) { 7 | c.stroke( 8 | "make yourselves sheep and the wolves will eat you ", 9 | "#000000", 10 | "#000000", 11 | ); 12 | c.noLoop(); 13 | } 14 | 15 | function draw(c) { 16 | if (x1 == 0 && y1 == 0) { 17 | return; 18 | } 19 | c.line(x1, y1, x2, y2); 20 | } 21 | 22 | function onMouseClick(c, event) { 23 | // set stroke color to one of the ansi colors, but not black (1) 24 | c.strokeFg("" + Math.ceil(c.random(1, 255))); 25 | x1 = x2; 26 | y1 = y2; 27 | if (event.button == "left") { 28 | x2 = c.mouseX; 29 | y2 = c.mouseY; 30 | } 31 | c.redraw(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/line/line.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | func main() { 11 | runal.Run(context.Background(), setup, draw) 12 | } 13 | 14 | func setup(c *runal.Canvas) {} 15 | 16 | func draw(c *runal.Canvas) { 17 | c.Clear() 18 | y1 := (math.Sin((float64(c.Framecount)*0.2+1000)*0.2)/2 + 0.5) * float64(c.Height) * 0.8 19 | x1 := (math.Cos((float64(c.Framecount)*0.2+1000)*0.2)/2 + 0.5) * float64(c.Width) * 0.8 20 | y2 := (math.Sin(float64(c.Framecount)*0.1)/2 + 0.5) * float64(c.Height) * 0.8 21 | x2 := (math.Cos(float64(c.Framecount)*0.1)/2 + 0.5) * float64(c.Width) * 0.8 22 | c.Line(int(x1), int(y1), int(x2), int(y2)) 23 | } 24 | -------------------------------------------------------------------------------- /examples/grid/grid.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | func main() { 11 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey), runal.WithOnMouseClick(onMouseClick)) 12 | } 13 | 14 | func setup(c *runal.Canvas) { 15 | c.NoLoop() 16 | } 17 | 18 | func draw(c *runal.Canvas) { 19 | c.Clear() 20 | for i := 0; i < c.Width; i++ { 21 | for j := 0; j < c.Height; j++ { 22 | if rand.Intn(100) < 80 { 23 | c.Text(".", i, j) 24 | } 25 | } 26 | } 27 | } 28 | 29 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 30 | if e.Key == "space" { 31 | c.Redraw() 32 | } 33 | } 34 | 35 | func onMouseClick(c *runal.Canvas, e runal.MouseEvent) { 36 | c.Redraw() 37 | } 38 | -------------------------------------------------------------------------------- /examples/glitch/glitch.js: -------------------------------------------------------------------------------- 1 | let img; 2 | let glitchPoints = 80; 3 | let glitchSize = 2; 4 | 5 | let glitchLines = 5; 6 | 7 | function setup(c) { 8 | img = c.loadImage("mona-lisa.jpg"); 9 | c.fps(5); 10 | } 11 | 12 | function draw(c) { 13 | c.clear(); 14 | c.image(img, c.random(-2, 2), c.random(-2, 2), c.width, c.height); 15 | 16 | for (let i = 0; i < glitchPoints; i++) { 17 | let part = c.get( 18 | c.random(0, c.width), 19 | c.random(0, c.height), 20 | glitchSize, 21 | c.random(1, glitchSize + 1), 22 | ); 23 | c.set(c.random(0, c.width), c.random(0, c.height), part); 24 | } 25 | 26 | for (let i = 0; i < glitchLines; i++) { 27 | let part = c.get(0, c.random(0, c.height), c.width, 1); 28 | c.set(0, c.random(0, c.height), part); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ffmpeg.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strconv" 7 | ) 8 | 9 | const ( 10 | ffmpegBinary = "ffmpeg" 11 | ) 12 | 13 | func checkFFMPEG() bool { 14 | _, err := exec.LookPath(ffmpegBinary) 15 | return err == nil 16 | } 17 | 18 | func framesToMP4Videos(fps int, input, output string) error { 19 | cmd := exec.Command(ffmpegBinary, buildArgs(fps, input, output)...) 20 | err := cmd.Run() 21 | if err != nil { 22 | return fmt.Errorf("ffmpeg error: %s", err.Error()) 23 | } 24 | return nil 25 | } 26 | 27 | func buildArgs(fps int, input, output string) []string { 28 | return []string{ 29 | "-framerate", 30 | strconv.Itoa(fps), 31 | "-y", 32 | "-i", 33 | input, 34 | "-c:v", 35 | "libx264", 36 | "-pix_fmt", 37 | "yuv420p", 38 | output, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/circle/circle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | func main() { 11 | runal.Run(context.Background(), setup, draw) 12 | } 13 | 14 | func setup(c *runal.Canvas) { 15 | c.CellModeCustom(".") 16 | } 17 | 18 | func draw(c *runal.Canvas) { 19 | c.Clear() 20 | 21 | radius1 := ((math.Sin(float64(c.Framecount)*0.1)*0.5 + 0.5) * float64(c.Width)) / 2 22 | radius2 := ((math.Sin(float64(c.Framecount)*0.2)*0.5 + 0.5) * float64(c.Width)) / 3 23 | 24 | c.Stroke("COUCOU", "#ffffff", "#000000") 25 | c.Fill("i", "#ffffff", "#000000") 26 | c.Circle(c.Width/2, c.Height/2, int(radius1)) 27 | 28 | c.Stroke("C", "#ffffff", "#000000") 29 | c.Fill("vvvv", "#ffffff", "#000000") 30 | c.Circle(c.Width/2, c.Height/2, int(radius2)) 31 | } 32 | -------------------------------------------------------------------------------- /examples/noiseloop1d/noiseloop1d.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | const ( 11 | duration = 1 12 | scale = 1 13 | ) 14 | 15 | func main() { 16 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 17 | } 18 | 19 | func setup(c *runal.Canvas) {} 20 | 21 | func draw(c *runal.Canvas) { 22 | c.Clear() 23 | c.StrokeText("THIS IS A 1D NOISE LOOP EXAMPLE") 24 | theta := c.LoopAngle(duration) 25 | 26 | for x := 0; x < c.Width; x++ { 27 | noise := c.NoiseLoop1D(theta, 0.1, x*scale) 28 | y := c.Map(noise, 0, 1, 0, float64(c.Height)) 29 | c.Point(x, int(y)) 30 | } 31 | } 32 | 33 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 34 | if e.Key == "space" { 35 | c.NoiseSeed(time.Now().Unix()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ansi.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | ) 8 | 9 | func clearScreen() { 10 | fmt.Print(ansi.ResetModeAltScreen) 11 | } 12 | 13 | func hideCursor() { 14 | fmt.Print(ansi.HideCursor) 15 | } 16 | 17 | func showCursor() { 18 | fmt.Print(ansi.ShowCursor) 19 | } 20 | 21 | func resetCursorPosition() { 22 | fmt.Print(ansi.CursorHomePosition) 23 | } 24 | 25 | func enterAltScreen() { 26 | fmt.Print(ansi.SetModeAltScreen) 27 | hideCursor() 28 | } 29 | 30 | func enableMouse() { 31 | fmt.Print(ansi.SetModeMouseAnyEvent) 32 | fmt.Print(ansi.SetModeMouseExtSgr) 33 | } 34 | 35 | func disableMouse() { 36 | fmt.Print(ansi.ResetModeMouseAnyEvent) 37 | fmt.Print(ansi.ResetModeMouseExtSgr) 38 | } 39 | 40 | func clearLineSequence() string { 41 | return "\r\n\x1b[2K" 42 | } 43 | -------------------------------------------------------------------------------- /examples/mouse/mouse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | func main() { 11 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey), runal.WithOnMouseClick(onMouseClick)) 12 | } 13 | 14 | func setup(c *runal.Canvas) {} 15 | 16 | func draw(c *runal.Canvas) { 17 | c.Clear() 18 | c.Circle(c.MouseX, c.MouseY, 5) 19 | } 20 | 21 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 22 | if e.Key == "space" { 23 | color := c.Random(0, 255) 24 | colorStr := strconv.FormatFloat(color, 'f', -1, 64) 25 | c.BackgroundBg(colorStr) 26 | } 27 | } 28 | 29 | func onMouseClick(c *runal.Canvas, e runal.MouseEvent) { 30 | color := c.Random(0, 255) 31 | colorStr := strconv.FormatFloat(color, 'f', -1, 64) 32 | c.BackgroundBg(colorStr) 33 | } 34 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const maxDebugBufferSize = 100 8 | 9 | var debugStyle = style{ 10 | background: color("9"), 11 | foreground: color("15"), 12 | } 13 | 14 | func (c *Canvas) Debug(messages ...any) { 15 | msg := fmt.Sprint(messages...) 16 | c.debugBuffer = append(c.debugBuffer, msg) 17 | 18 | if len(c.debugBuffer) > min(maxDebugBufferSize, c.termHeight) { 19 | c.debugBuffer = c.debugBuffer[len(c.debugBuffer)-min(maxDebugBufferSize, c.termHeight):] 20 | } 21 | } 22 | 23 | func (c *Canvas) renderDebug() { 24 | if len(c.debugBuffer) == 0 { 25 | return 26 | } 27 | resetCursorPosition() 28 | for y, msg := range c.debugBuffer { 29 | if y >= c.termHeight-1 { 30 | continue 31 | } 32 | fmt.Fprint(c.output, debugStyle.render(fmt.Sprintf("%s\r\n", msg))) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cli/demo.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.savedCanvasFontSize(24); 3 | c.cellModeDouble(); 4 | } 5 | 6 | function draw(c) { 7 | for (let i = 0; i < c.width; i++) { 8 | for (let j = 0; j < c.height; j++) { 9 | let color = c.map( 10 | c.noise2D( 11 | i * 0.009 + c.framecount / 1000, 12 | j * 0.009 + c.framecount / 1000, 13 | ), 14 | 0, 15 | 1, 16 | 150, 17 | 231, 18 | ); 19 | c.stroke("§", color, "#000000"); 20 | c.point(i, j); 21 | } 22 | } 23 | } 24 | 25 | function onKey(c, e) { 26 | if (e.Key == "c") { 27 | c.saveCanvasToPNG(`canvas_${Date.now()}.png`); 28 | } 29 | if (e.key == "space") { 30 | c.noiseSeed(Date.now()); 31 | c.redraw(); 32 | } 33 | } 34 | 35 | function onMouseClick(c, e) { 36 | c.noiseSeed(Date.now()); 37 | c.redraw(); 38 | } 39 | -------------------------------------------------------------------------------- /examples/noise/noise.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.savedCanvasFontSize(24); 3 | c.cellModeDouble(); 4 | } 5 | 6 | function draw(c) { 7 | for (let i = 0; i < c.width; i++) { 8 | for (let j = 0; j < c.height; j++) { 9 | let color = c.map( 10 | c.noise2D( 11 | i * 0.009 + c.framecount / 1000, 12 | j * 0.009 + c.framecount / 1000, 13 | ), 14 | 0, 15 | 1, 16 | 150, 17 | 231, 18 | ); 19 | c.stroke("§", color, "#000000"); 20 | c.point(i, j); 21 | } 22 | } 23 | } 24 | 25 | function onKey(c, e) { 26 | if (e.Key == "c") { 27 | c.saveCanvasToPNG(`canvas_${Date.now()}.png`); 28 | } 29 | if (e.key == "space") { 30 | c.noiseSeed(Date.now()); 31 | c.redraw(); 32 | } 33 | } 34 | 35 | function onMouseClick(c, e) { 36 | c.noiseSeed(Date.now()); 37 | c.redraw(); 38 | } 39 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | type frame [][]cell 4 | 5 | func (f frame) size() (int, int) { 6 | return len(f[0]), len(f) 7 | } 8 | 9 | func (f frame) outOfBounds(x, y int) bool { 10 | w, h := f.size() 11 | return x < 0 || y < 0 || x >= w || y >= h 12 | } 13 | 14 | func newFrame(width, height int) frame { 15 | buff := make([][]cell, height) 16 | for i := range buff { 17 | buff[i] = make([]cell, width) 18 | } 19 | return buff 20 | } 21 | 22 | func (c *Canvas) Get(x, y, w, h int) Image { 23 | frame := newFrame(w, h) 24 | for fy := range frame { 25 | for fx := range frame[fy] { 26 | if c.outOfBounds(x+fx, y+fy) { 27 | continue 28 | } 29 | frame[fy][fx] = c.buffer[y+fy][x+fx] 30 | } 31 | } 32 | return &imageFrame{ 33 | frame: frame, 34 | } 35 | } 36 | 37 | func (c *Canvas) Set(x, y int, cells writable) { 38 | cells.write(c, x, y, 0, 0) 39 | } 40 | -------------------------------------------------------------------------------- /examples/draw/draw.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "strconv" 7 | 8 | "github.com/emprcl/runal" 9 | ) 10 | 11 | var x1, y1, x2, y2 int 12 | 13 | func main() { 14 | runal.Run(context.Background(), setup, draw, runal.WithOnMouseClick(onMouseClick)) 15 | } 16 | 17 | func setup(c *runal.Canvas) { 18 | c.Stroke("make yourselves sheep and the wolves will eat you ", "#000000", "#000000") 19 | c.NoLoop() 20 | } 21 | 22 | func draw(c *runal.Canvas) { 23 | if x1 == 0 && y1 == 0 { 24 | return 25 | } 26 | c.Line(x1, y1, x2, y2) 27 | } 28 | 29 | func onMouseClick(c *runal.Canvas, e runal.MouseEvent) { 30 | // set stroke color to one of the ansi colors, but not black (1) 31 | c.StrokeFg(strconv.Itoa(int(math.Ceil(c.Random(1, 255))))) 32 | x1 = x2 33 | y1 = y2 34 | if e.Button == "left" { 35 | x2 = c.MouseX 36 | y2 = c.MouseY 37 | } 38 | c.Redraw() 39 | } 40 | -------------------------------------------------------------------------------- /examples/gif-export/gif-export.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "time" 7 | 8 | "github.com/emprcl/runal" 9 | ) 10 | 11 | const ( 12 | duration = 5 13 | margin = 3 14 | ) 15 | 16 | func main() { 17 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 18 | } 19 | 20 | func setup(c *runal.Canvas) { 21 | c.Size(40, 21) 22 | c.BackgroundBg("197") 23 | c.SaveCanvasToGIF("canvas.gif", duration) 24 | } 25 | 26 | func draw(c *runal.Canvas) { 27 | c.Clear() 28 | c.Stroke("RUNAL", "255", "197") 29 | theta := c.LoopAngle(duration) 30 | 31 | for y := margin; y < c.Height-margin; y++ { 32 | x := c.Map(math.Sin(theta+float64(y)), -1, 1, margin, float64(c.Width-margin)) 33 | c.Point(int(x), y) 34 | } 35 | } 36 | 37 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 38 | if e.Key == "space" { 39 | c.NoiseSeed(time.Now().Unix()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/glitch/glitch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | func main() { 10 | runal.Run(context.Background(), setup, draw) 11 | } 12 | 13 | var img runal.Image 14 | 15 | const ( 16 | glitchPoints = 80 17 | glitchSize = 2 18 | glitchLines = 5 19 | ) 20 | 21 | func setup(c *runal.Canvas) { 22 | img = c.LoadImage("mona-lisa.jpg") 23 | c.Fps(5) 24 | } 25 | 26 | func draw(c *runal.Canvas) { 27 | c.Image(img, int(c.Random(-2, 2)), int(c.Random(-2, 2)), c.Width, c.Height) 28 | 29 | for range glitchPoints { 30 | part := c.Get(int(c.Random(0, c.Width)), int(c.Random(0, c.Height)), glitchSize, int(c.Random(1, glitchSize+1))) 31 | c.Set(int(c.Random(0, c.Width)), int(c.Random(0, c.Height)), part) 32 | } 33 | 34 | for range glitchLines { 35 | part := c.Get(0, int(c.Random(0, c.Height)), c.Width, 1) 36 | c.Set(0, int(c.Random(0, c.Height)), part) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/charmbracelet/log" 8 | 9 | "golang.org/x/term" 10 | ) 11 | 12 | func termSize() (int, int) { 13 | w, h, err := term.GetSize(int(os.Stdout.Fd())) 14 | if err != nil { 15 | log.Fatal("can't read terminal size") 16 | } 17 | return w, h 18 | } 19 | 20 | func forcePadding(s *strings.Builder, sLength, tLength int, padChar rune) { 21 | if sLength >= tLength { 22 | return 23 | } 24 | padding := strings.Repeat(string(padChar), tLength-sLength) 25 | s.WriteString(padding) 26 | } 27 | 28 | func absInt(a int) int { 29 | if a < 0 { 30 | return -a 31 | } 32 | return a 33 | } 34 | 35 | func strIndex(str string, index int) rune { 36 | return rune(str[index%len(str)]) 37 | } 38 | 39 | func randomDir() string { 40 | tmp, err := os.MkdirTemp(os.TempDir(), "runal") 41 | if err != nil { 42 | log.Fatalf("error creating temporary directory: %v", err) 43 | } 44 | return tmp 45 | } 46 | -------------------------------------------------------------------------------- /examples/noiseloop2d/noiseloop2d.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/emprcl/runal" 9 | ) 10 | 11 | const ( 12 | duration = 3 13 | scale = 0.3 14 | ) 15 | 16 | func main() { 17 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 18 | } 19 | 20 | func setup(c *runal.Canvas) {} 21 | 22 | func draw(c *runal.Canvas) { 23 | c.Clear() 24 | theta := c.LoopAngle(duration) 25 | 26 | for x := 0; x < c.Width; x++ { 27 | for y := 0; y < c.Height; y++ { 28 | noise := c.NoiseLoop2D( 29 | theta, 30 | 1, 31 | int(float64(x)*scale), 32 | int(float64(y)*scale), 33 | ) 34 | color := c.Map(noise, 0, 1, 232, 255) 35 | colorStr := strconv.FormatFloat(color, 'f', -1, 64) 36 | c.Stroke("§", colorStr, "0") 37 | c.Point(x, y) 38 | } 39 | } 40 | } 41 | 42 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 43 | if e.Key == "space" { 44 | c.NoiseSeed(time.Now().Unix()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cell.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | ) 8 | 9 | type Cell struct { 10 | Char string 11 | Foreground string 12 | Background string 13 | } 14 | 15 | func (cll Cell) write(c *Canvas, x, y, _, _ int) { 16 | if c.outOfBounds(x, y) { 17 | return 18 | } 19 | c.write(cll.private(), x, y, 1) 20 | } 21 | 22 | func (cll Cell) private() cell { 23 | return cell{ 24 | char: []rune(cll.Char)[0], 25 | foreground: color(cll.Foreground), 26 | background: color(cll.Background), 27 | } 28 | } 29 | 30 | type cell struct { 31 | char rune 32 | padChar rune 33 | foreground ansi.Color 34 | background ansi.Color 35 | } 36 | 37 | func (c cell) public() Cell { 38 | return Cell{ 39 | Char: string(c.char), 40 | Foreground: colorToString(c.foreground), 41 | Background: colorToString(c.background), 42 | } 43 | } 44 | 45 | func colorToString(c ansi.Color) string { 46 | r, g, b, _ := c.RGBA() 47 | return fmt.Sprintf("#%d%d%d", r, g, b) 48 | } 49 | -------------------------------------------------------------------------------- /examples/noiseloop-exportmp4/noiseloop-exportmp4.js: -------------------------------------------------------------------------------- 1 | let duration = 5; 2 | let radius = 4; 3 | let seed1 = Date.now(); 4 | let seed2 = seed1 + 1000; 5 | 6 | function setup(c) { 7 | c.cellModeDouble(); 8 | } 9 | 10 | function draw(c) { 11 | c.clear(); 12 | 13 | let theta = c.loopAngle(duration); 14 | c.noiseSeed(seed1); 15 | let noise = c.noiseLoop(theta, 1); 16 | c.noiseSeed(seed2); 17 | let noise2 = c.noiseLoop(theta, 1); 18 | let x = c.map(noise, 0, 1, radius, c.width - radius); 19 | let y = c.map(noise2, 0, 1, radius, c.height - radius); 20 | let color = c.map(noise, 0, 1, 0, 10); 21 | let colorBg = c.map(noise2, 0, 1, 200, 210); 22 | 23 | c.background("#", colorBg, colorBg); 24 | c.stroke(" ", color, color); 25 | c.fill(" ", color, color); 26 | c.circle(x, y, radius); 27 | } 28 | 29 | function onKey(c, e) { 30 | if (e.key == "space") { 31 | seed1 = Date.now(); 32 | seed2 = seed1 + 1000; 33 | } 34 | if (e.key == "c") { 35 | c.saveCanvasToMP4("flash.mp4", duration); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/world/world.js: -------------------------------------------------------------------------------- 1 | function setup(c) { 2 | c.size(82, 41); 3 | c.background("/", "237", "#000000"); 4 | c.stroke("0", "255", "#000000"); 5 | c.cellModeDouble(); 6 | } 7 | 8 | function draw(c) { 9 | c.clear(); 10 | if (c.framecount % 30 < 15) { 11 | c.backgroundText("\\"); 12 | } else { 13 | c.backgroundText("/"); 14 | } 15 | let theta = c.loopAngle(10); 16 | for (let i = 0; i < c.width; i++) { 17 | for (let j = 0; j < c.height; j++) { 18 | let color = c.map( 19 | c.noise2D( 20 | c.framecount * 0.03 + i * 0.05, 21 | c.framecount * 0.03 + j * 0.05, 22 | ), 23 | 0, 24 | 1, 25 | 231, 26 | 255, 27 | ); 28 | if (c.dist(i, j, c.width / 2, c.height / 2) <= c.width / 2 - 4) { 29 | c.strokeFg(color); 30 | c.point(i, j); 31 | } 32 | } 33 | } 34 | } 35 | 36 | function onKey(c, e) { 37 | if (e.key == "c") { 38 | c.saveCanvasToPNG(`canvas_${Date.now()}.png`); 39 | } 40 | if (e.key == "space") { 41 | c.noiseSeed(Date.now()); 42 | c.redraw(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Xavier Godart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/noise/noise.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/emprcl/runal" 10 | ) 11 | 12 | func main() { 13 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 14 | } 15 | 16 | func setup(c *runal.Canvas) { 17 | c.SavedCanvasFontSize(24) 18 | c.CellModeDouble() 19 | } 20 | 21 | func draw(c *runal.Canvas) { 22 | for i := 0; i < c.Width; i++ { 23 | for j := 0; j < c.Height; j++ { 24 | color := c.Map( 25 | c.Noise2D( 26 | float64(i)*0.009+float64(c.Framecount)/1000, 27 | float64(j)*0.009+float64(c.Framecount)/1000, 28 | ), 29 | 0, 30 | 1, 31 | 150, 32 | 231, 33 | ) 34 | colorStr := strconv.FormatFloat(color, 'f', -1, 64) 35 | c.Stroke("§", colorStr, "#000000") 36 | c.Point(i, j) 37 | } 38 | } 39 | } 40 | 41 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 42 | if e.Key == "c" { 43 | filename := fmt.Sprintf("canvas_%d.png", time.Now().Unix()) 44 | c.SaveCanvasToPNG(filename) 45 | } 46 | if e.Key == "space" { 47 | c.NoiseSeed(time.Now().Unix()) 48 | c.Redraw() 49 | } 50 | } 51 | 52 | func onMouseClick(c *runal.Canvas, e runal.MouseEvent) { 53 | c.NoiseSeed(time.Now().Unix()) 54 | c.Redraw() 55 | } 56 | -------------------------------------------------------------------------------- /examples/lissajous/lissajous.js: -------------------------------------------------------------------------------- 1 | let duration = 5; 2 | let margin = 3; 3 | let pointsNb = 26; 4 | let points = []; 5 | 6 | let a = 5; 7 | let b = 2; 8 | 9 | function setup(c) { 10 | c.size(40, 20); 11 | c.backgroundBg("197"); 12 | c.stroke(".", "255", "197"); 13 | } 14 | 15 | function draw(c) { 16 | c.clear(); 17 | 18 | let theta = c.loopAngle(duration); 19 | let x = c.map(Math.sin(a * theta), -1, 1, margin, c.width - margin); 20 | let y = c.map(Math.sin(b * theta), -1, 1, margin, c.height - margin); 21 | let x2 = c.map(Math.sin(b * theta), -1, 1, margin, c.width - margin); 22 | let y2 = c.map(Math.sin(a * theta), -1, 1, margin, c.height - margin); 23 | if (points.length <= pointsNb * 2) { 24 | points.push(new Point(x, y, "0")); 25 | points.push(new Point(x2, y2, "#")); 26 | } 27 | 28 | for (let i = 0; i < points.length; i++) { 29 | points[i].update(c); 30 | } 31 | } 32 | 33 | class Point { 34 | constructor(x, y, char) { 35 | this.x = x; 36 | this.y = y; 37 | this.char = char; 38 | this.duration = pointsNb; 39 | } 40 | 41 | update(c) { 42 | c.strokeText(this.char); 43 | c.point(this.x, this.y); 44 | this.duration--; 45 | if (this.duration < 0) { 46 | points.shift(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := bin 2 | BIN_NAME := runal 3 | CLI_DIR := cli 4 | CLI_ENTRYPOINT := . 5 | GOLANG_BIN := go 6 | CGO_ENABLED := 0 7 | GOLANG_OS := linux 8 | GOLANG_ARCH := amd64 9 | GOLANG_BUILD_OPTS += GOOS=$(GOLANG_OS) 10 | GOLANG_BUILD_OPTS += GOARCH=$(GOLANG_ARCH) 11 | GOLANG_BUILD_OPTS += CGO_ENABLED=$(CGO_ENABLED) 12 | GOLANG_LINT := $(BIN)/golangci-lint 13 | GOLANG_LDFFLAGS := -ldflags="-w -s" 14 | 15 | $(BIN): 16 | mkdir -p $(BIN) 17 | 18 | $(GOLANG_LINT): $(BIN) 19 | GOBIN=$$(pwd)/$(BIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest 20 | 21 | build: $(BIN) 22 | $(GOLANG_BUILD_OPTS) $(GOLANG_BIN) build -C $(CLI_DIR) $(GOLANG_LDFFLAGS) -o ../$(BIN)/$(BIN_NAME) $(CLI_ENTRYPOINT) 23 | chmod +x $(BIN)/$(BIN_NAME) 24 | 25 | checks: $(GOLANG_LINT) 26 | $(GOLANG_LINT) run ./... 27 | 28 | test: 29 | $(GOLANG_BIN) test ./... 30 | 31 | bench: 32 | $(GOLANG_BIN) test -bench=. -benchmem ./... 33 | 34 | deps: 35 | @find . -type d \( -name build -prune \) -o -name go.mod -print | while read -r gomod_path; do \ 36 | dir_path=$$(dirname "$$gomod_path"); \ 37 | echo "Executing 'go mod tidy' in directory: $$dir_path"; \ 38 | (cd "$$dir_path" && go get -u ./... && go mod tidy) || exit 1; \ 39 | done 40 | -------------------------------------------------------------------------------- /cli/runtime/javascript.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/dop251/goja" 7 | ) 8 | 9 | type callbacks struct { 10 | onKey goja.Callable 11 | onMouseMove goja.Callable 12 | onMouseClick goja.Callable 13 | onMouseRelease goja.Callable 14 | onMouseWheel goja.Callable 15 | } 16 | 17 | func parseJS(script string) (*goja.Runtime, goja.Callable, goja.Callable, callbacks, error) { 18 | vm := goja.New() 19 | vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) 20 | _, err := vm.RunString(script) 21 | 22 | cb := callbacks{} 23 | 24 | if err != nil { 25 | return nil, nil, nil, cb, err 26 | } 27 | setup, ok := goja.AssertFunction(vm.Get("setup")) 28 | if !ok { 29 | return nil, nil, nil, cb, errors.New("The file does not contain a setup method") 30 | } 31 | 32 | draw, ok := goja.AssertFunction(vm.Get("draw")) 33 | if !ok { 34 | return nil, nil, nil, cb, errors.New("The file does not contain a draw method") 35 | } 36 | 37 | cb.onKey, _ = goja.AssertFunction(vm.Get("onKey")) 38 | cb.onMouseMove, _ = goja.AssertFunction(vm.Get("onMouseMove")) 39 | cb.onMouseClick, _ = goja.AssertFunction(vm.Get("onMouseClick")) 40 | cb.onMouseRelease, _ = goja.AssertFunction(vm.Get("onMouseRelease")) 41 | cb.onMouseWheel, _ = goja.AssertFunction(vm.Get("onMouseWheel")) 42 | 43 | return vm, setup, draw, cb, nil 44 | } 45 | -------------------------------------------------------------------------------- /examples/spiral/spiral.js: -------------------------------------------------------------------------------- 1 | let cols, rows, size; 2 | let t = 0; 3 | let background = "#000000"; 4 | let colors = ["#fcf6bd", "#d0f4de", "#a9def9", "#e4c1f9", "#ff99c8"]; 5 | 6 | function setup(c) { 7 | c.background(" ", background, background); 8 | c.cellModeCustom(" "); 9 | } 10 | 11 | function draw(c) { 12 | c.clear(); 13 | size = 1; 14 | cols = c.width / size; 15 | rows = c.height / size; 16 | 17 | for (let i = 0; i < cols; i++) { 18 | for (let j = 0; j < cols; j++) { 19 | let x = i * size; 20 | let y = j * size; 21 | let d = c.dist(x, y, c.width / 2, c.height / 2); 22 | let k = 0.6; 23 | let dx = x - c.width / 2; 24 | let dy = y - c.height / 2; 25 | let angle = Math.atan2(dy, dx); 26 | let spiralPath = Math.sin(d / k + angle + t); 27 | let df = 2; 28 | let af = 2; 29 | threshold = Math.sin(d / df + af * angle); 30 | 31 | c.stroke("⬤", colorGradient(c.width, d), background); 32 | 33 | if (spiralPath > threshold) { 34 | c.point(x, y); 35 | } 36 | } 37 | } 38 | 39 | t += 0.5; 40 | } 41 | 42 | function colorGradient(width, d) { 43 | let step = width / colors.length; 44 | for (let i = 0; i < colors.length; i++) { 45 | if (d <= (i + 1) * step) { 46 | return colors[i]; 47 | } 48 | } 49 | return colors[colors.length - 1]; 50 | } 51 | -------------------------------------------------------------------------------- /examples/world/world.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/emprcl/runal" 10 | ) 11 | 12 | func main() { 13 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 14 | } 15 | 16 | func setup(c *runal.Canvas) { 17 | c.Size(82, 41) 18 | c.Background("/", "237", "#000000") 19 | c.Stroke("0", "255", "#000000") 20 | c.CellModeDouble() 21 | } 22 | 23 | func draw(c *runal.Canvas) { 24 | c.Clear() 25 | if c.Framecount%30 < 15 { 26 | c.BackgroundText("\\") 27 | } else { 28 | c.BackgroundText("/") 29 | } 30 | // theta := c.LoopAngle(10) 31 | for i := 0; i < c.Width; i++ { 32 | for j := 0; j < c.Height; j++ { 33 | color := c.Map( 34 | c.Noise2D( 35 | float64(c.Framecount)*0.03+float64(i)*0.05, 36 | float64(c.Framecount)*0.03+float64(j)*0.05, 37 | ), 38 | 0, 39 | 1, 40 | 231, 41 | 255, 42 | ) 43 | colorStr := strconv.FormatFloat(color, 'f', -1, 64) 44 | if c.Dist(i, j, c.Width/2, c.Height/2) <= float64(c.Width)/2-4.0 { 45 | c.StrokeFg(colorStr) 46 | c.Point(i, j) 47 | } 48 | } 49 | } 50 | } 51 | 52 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 53 | if e.Key == "c" { 54 | filename := fmt.Sprintf("canvas_%d.png", time.Now().Unix()) 55 | c.SaveCanvasToPNG(filename) 56 | } 57 | if e.Key == "space" { 58 | c.NoiseSeed(time.Now().Unix()) 59 | c.Redraw() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/noiseloop-exportmp4/noiseloop-exportmp4.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/emprcl/runal" 9 | ) 10 | 11 | const ( 12 | duration = 5 13 | radius = 4 14 | ) 15 | 16 | var seed1, seed2 int64 17 | 18 | func main() { 19 | seed1 = time.Now().Unix() 20 | seed2 = seed1 + 1000 21 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 22 | } 23 | 24 | func setup(c *runal.Canvas) { 25 | c.CellModeDouble() 26 | } 27 | 28 | func draw(c *runal.Canvas) { 29 | c.Clear() 30 | 31 | theta := c.LoopAngle(duration) 32 | c.NoiseSeed(seed1) 33 | noise := c.NoiseLoop(theta, 1) 34 | c.NoiseSeed(seed2) 35 | noise2 := c.NoiseLoop(theta, 1) 36 | x := c.Map(noise, 0, 1, radius, float64(c.Width-radius)) 37 | y := c.Map(noise2, 0, 1, radius, float64(c.Height-radius)) 38 | color := c.Map(noise, 0, 1, 0, 10) 39 | colorStr := strconv.FormatFloat(color, 'f', -1, 64) 40 | colorBg := c.Map(noise2, 0, 1, 200, 210) 41 | colorBgStr := strconv.FormatFloat(colorBg, 'f', -1, 64) 42 | 43 | c.Background("#", colorBgStr, colorBgStr) 44 | c.Stroke(" ", colorStr, colorStr) 45 | c.Fill(" ", colorStr, colorStr) 46 | c.Circle(int(x), int(y), radius) 47 | } 48 | 49 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 50 | if e.Key == "space" { 51 | seed1 = time.Now().Unix() 52 | seed2 = seed1 + 1000 53 | } 54 | if e.Key == "c" { 55 | c.SaveCanvasToMP4("flash.mp4", duration) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/spiral/spiral.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | 7 | "github.com/emprcl/runal" 8 | ) 9 | 10 | var ( 11 | t float64 12 | background string = "#000000" 13 | colors []string = []string{ 14 | "#fcf6bd", 15 | "#d0f4de", 16 | "#a9def9", 17 | "#e4c1f9", 18 | "#ff99c8", 19 | } 20 | ) 21 | 22 | func main() { 23 | runal.Run(context.Background(), setup, draw) 24 | } 25 | 26 | func setup(c *runal.Canvas) { 27 | c.Background(" ", background, background) 28 | c.CellModeCustom(" ") 29 | } 30 | 31 | func draw(c *runal.Canvas) { 32 | c.Clear() 33 | size := 1 34 | cols := int(math.Round(float64(c.Width) / float64(size))) 35 | rows := int(math.Round(float64(c.Height) / float64(size))) 36 | for i := 0; i < cols; i++ { 37 | for j := 0; j < rows; j++ { 38 | x := i * size 39 | y := j * size 40 | d := c.Dist(x, y, c.Width/2, c.Height/2) 41 | k := .6 42 | dx := float64(x) - float64(c.Width)/2. 43 | dy := float64(y) - float64(c.Height)/2. 44 | angle := math.Atan2(dy, dx) 45 | spiralPath := math.Sin(d/k + angle + t) 46 | df := 2. 47 | af := 2. 48 | threshold := math.Sin(d/df + af*angle) 49 | 50 | c.Stroke("⬤", colorGradient(c.Width, d), background) 51 | 52 | if spiralPath > threshold { 53 | c.Point(x, y) 54 | } 55 | } 56 | } 57 | 58 | t += 0.5 59 | } 60 | 61 | func colorGradient(width int, d float64) string { 62 | step := width / len(colors) 63 | for i := 0; i < len(colors); i++ { 64 | if d <= float64((i+1)*step) { 65 | return colors[i] 66 | } 67 | } 68 | return colors[len(colors)-1] 69 | } 70 | -------------------------------------------------------------------------------- /input.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "sync" 7 | 8 | "github.com/charmbracelet/log" 9 | "github.com/charmbracelet/x/input" 10 | "github.com/charmbracelet/x/term" 11 | ) 12 | 13 | type KeyEvent struct { 14 | Key string 15 | Code int 16 | } 17 | 18 | type MouseEvent struct { 19 | X int 20 | Y int 21 | Button string 22 | } 23 | 24 | func listenForInputEvents(ctx context.Context, wg *sync.WaitGroup) chan input.Event { 25 | inputEvents := make(chan input.Event, 2048) 26 | wg.Add(1) 27 | go func() { 28 | defer wg.Done() 29 | defer close(inputEvents) 30 | 31 | state, err := term.MakeRaw(os.Stdin.Fd()) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | defer term.Restore(os.Stdin.Fd(), state) // nolint: errcheck 36 | 37 | reader, err := input.NewReader(os.Stdin, os.Getenv("TERM"), input.FlagMouseMode) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | defer reader.Close() 42 | 43 | readerEvents := make(chan []input.Event, 8) 44 | readerErrors := make(chan error, 8) 45 | go func() { 46 | for { 47 | select { 48 | case <-ctx.Done(): 49 | return 50 | default: 51 | } 52 | events, err := reader.ReadEvents() 53 | if err != nil { 54 | readerErrors <- err 55 | return 56 | } 57 | readerEvents <- events 58 | } 59 | }() 60 | 61 | for { 62 | select { 63 | case <-ctx.Done(): 64 | return 65 | case events := <-readerEvents: 66 | for _, ev := range events { 67 | inputEvents <- ev 68 | } 69 | case err := <-readerErrors: 70 | log.Fatal(err) 71 | } 72 | } 73 | }() 74 | return inputEvents 75 | } 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/emprcl/runal 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/aquilax/go-perlin v1.1.0 7 | github.com/charmbracelet/log v0.4.2 8 | github.com/charmbracelet/x/ansi v0.11.0 9 | github.com/charmbracelet/x/input v0.3.7 10 | github.com/charmbracelet/x/term v0.2.2 11 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb 12 | golang.org/x/image v0.33.0 13 | golang.org/x/term v0.37.0 14 | ) 15 | 16 | require ( 17 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 18 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 19 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 20 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 21 | github.com/charmbracelet/x/windows v0.2.2 // indirect 22 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 23 | github.com/clipperhouse/stringish v0.1.1 // indirect 24 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 25 | github.com/fogleman/gg v1.3.0 // indirect 26 | github.com/go-logfmt/logfmt v0.6.1 // indirect 27 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 28 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 29 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 30 | github.com/mattn/go-isatty v0.0.20 // indirect 31 | github.com/mattn/go-runewidth v0.0.19 // indirect 32 | github.com/muesli/cancelreader v0.2.2 // indirect 33 | github.com/muesli/termenv v0.16.0 // indirect 34 | github.com/pkg/errors v0.9.1 // indirect 35 | github.com/rivo/uniseg v0.4.7 // indirect 36 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 37 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /examples/lissajous/lissajous.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "time" 7 | 8 | "github.com/emprcl/runal" 9 | ) 10 | 11 | const ( 12 | duration = 5 13 | margin = 3 14 | pointsNb = 26 15 | a = 5 16 | b = 2 17 | ) 18 | 19 | type point struct { 20 | x, y float64 21 | char string 22 | duration int 23 | } 24 | 25 | func (p *point) update(c *runal.Canvas) { 26 | c.StrokeText(p.char) 27 | c.Point(int(p.x), int(p.y)) 28 | p.duration-- 29 | } 30 | 31 | var points = []point{} 32 | 33 | func main() { 34 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey)) 35 | } 36 | 37 | func setup(c *runal.Canvas) { 38 | c.Size(40, 20) 39 | c.BackgroundBg("197") 40 | c.Stroke(".", "255", "197") 41 | } 42 | 43 | func draw(c *runal.Canvas) { 44 | c.Clear() 45 | theta := c.LoopAngle(duration) 46 | x := c.Map(math.Sin(a*theta), -1, 1, margin, float64(c.Width-margin)) 47 | y := c.Map(math.Sin(b*theta), -1, 1, margin, float64(c.Height-margin)) 48 | x2 := c.Map(math.Sin(b*theta), -1, 1, margin, float64(c.Width-margin)) 49 | y2 := c.Map(math.Sin(a*theta), -1, 1, margin, float64(c.Height-margin)) 50 | if len(points) <= pointsNb*2 { 51 | points = append(points, point{x, y, "0", pointsNb}) 52 | points = append(points, point{x2, y2, "#", pointsNb}) 53 | } 54 | 55 | for i := range points { 56 | points[i].update(c) 57 | } 58 | 59 | // remove points with a duration of zero 60 | filtered := points[:0] 61 | for _, p := range points { 62 | if p.duration > 0 { 63 | filtered = append(filtered, p) 64 | } 65 | } 66 | points = filtered 67 | } 68 | 69 | func onKey(c *runal.Canvas, e runal.KeyEvent) { 70 | if e.Key == "space" { 71 | c.NoiseSeed(time.Now().Unix()) 72 | c.Redraw() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /structure.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | type state struct { 6 | strokeFg, strokeBg ansi.Color 7 | fillFg, fillBg ansi.Color 8 | backgroundFg, backgroundBg ansi.Color 9 | strokeText, fillText, backgroundText string 10 | 11 | originX, originY int 12 | rotationAngle float64 13 | scale float64 14 | 15 | fill bool 16 | stroke bool 17 | } 18 | 19 | // Push saves the current transformation state. 20 | func (c *Canvas) Push() { 21 | c.state = &state{ 22 | strokeFg: c.strokeFg, 23 | strokeBg: c.strokeBg, 24 | fillFg: c.fillFg, 25 | fillBg: c.fillBg, 26 | backgroundFg: c.backgroundFg, 27 | backgroundBg: c.backgroundBg, 28 | strokeText: c.strokeText, 29 | fillText: c.fillText, 30 | backgroundText: c.backgroundText, 31 | originX: c.originX, 32 | originY: c.originY, 33 | rotationAngle: c.rotationAngle, 34 | scale: c.scale, 35 | fill: c.fill, 36 | stroke: c.stroke, 37 | } 38 | } 39 | 40 | // Pop restores the previous transformation state. 41 | func (c *Canvas) Pop() { 42 | if c.state == nil { 43 | return 44 | } 45 | 46 | c.strokeFg = c.state.strokeFg 47 | c.strokeBg = c.state.strokeBg 48 | c.fillFg = c.state.fillFg 49 | c.fillBg = c.state.fillBg 50 | c.backgroundFg = c.state.backgroundFg 51 | c.backgroundBg = c.state.backgroundBg 52 | c.strokeText = c.state.strokeText 53 | c.fillText = c.state.fillText 54 | c.backgroundText = c.state.backgroundText 55 | c.originX = c.state.originX 56 | c.originY = c.state.originY 57 | c.rotationAngle = c.state.rotationAngle 58 | c.scale = c.state.scale 59 | c.fill = c.state.fill 60 | c.stroke = c.state.stroke 61 | 62 | c.state = nil 63 | } 64 | -------------------------------------------------------------------------------- /examples/bezier/go.mod: -------------------------------------------------------------------------------- 1 | module bezier 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/circle/go.mod: -------------------------------------------------------------------------------- 1 | module circle 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/colors/go.mod: -------------------------------------------------------------------------------- 1 | module colors 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/draw/go.mod: -------------------------------------------------------------------------------- 1 | module draw 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/grid/go.mod: -------------------------------------------------------------------------------- 1 | module grid 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/line/go.mod: -------------------------------------------------------------------------------- 1 | module line 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/mouse/go.mod: -------------------------------------------------------------------------------- 1 | module mouse 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/noise/go.mod: -------------------------------------------------------------------------------- 1 | module noise 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/quad/go.mod: -------------------------------------------------------------------------------- 1 | module quad 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/scale/go.mod: -------------------------------------------------------------------------------- 1 | module scale 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/spiral/go.mod: -------------------------------------------------------------------------------- 1 | module spiral 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/square/go.mod: -------------------------------------------------------------------------------- 1 | module square 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/world/go.mod: -------------------------------------------------------------------------------- 1 | module world 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/triangle/go.mod: -------------------------------------------------------------------------------- 1 | module triangle 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/gif-export/go.mod: -------------------------------------------------------------------------------- 1 | module gif-export 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/glitch/go.mod: -------------------------------------------------------------------------------- 1 | module glitch-example 2 | 3 | go 1.24.4 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/image/go.mod: -------------------------------------------------------------------------------- 1 | module image-example 2 | 3 | go 1.24.4 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/lissajous/go.mod: -------------------------------------------------------------------------------- 1 | module lissajous 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/noiseloop1d/go.mod: -------------------------------------------------------------------------------- 1 | module noiseloop1d 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/noiseloop2d/go.mod: -------------------------------------------------------------------------------- 1 | module noiseloop2d 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/strokefill/go.mod: -------------------------------------------------------------------------------- 1 | module strokefill 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/translate/go.mod: -------------------------------------------------------------------------------- 1 | module translate 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/ellipse-rotation/go.mod: -------------------------------------------------------------------------------- 1 | module ellipse-rotation 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /examples/noiseloop-exportmp4/go.mod: -------------------------------------------------------------------------------- 1 | module noiseloop-exportmp4 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../../ 6 | 7 | require github.com/emprcl/runal v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/aquilax/go-perlin v1.1.0 // indirect 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 | github.com/charmbracelet/log v0.4.2 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 17 | github.com/charmbracelet/x/input v0.3.7 // indirect 18 | github.com/charmbracelet/x/term v0.2.2 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/fogleman/gg v1.3.0 // indirect 24 | github.com/go-logfmt/logfmt v0.6.1 // indirect 25 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 26 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 27 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mattn/go-runewidth v0.0.19 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/rivo/uniseg v0.4.7 // indirect 34 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 36 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 37 | golang.org/x/image v0.33.0 // indirect 38 | golang.org/x/sys v0.38.0 // indirect 39 | golang.org/x/term v0.37.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /cli/go.mod: -------------------------------------------------------------------------------- 1 | module runal-cli 2 | 3 | go 1.24.2 4 | 5 | replace github.com/emprcl/runal => ../ 6 | 7 | require ( 8 | github.com/charmbracelet/lipgloss v1.1.0 9 | github.com/charmbracelet/log v0.4.2 10 | github.com/dop251/goja v0.0.0-20251103141225-af2ceb9156d7 11 | github.com/emprcl/runal v0.0.0-00010101000000-000000000000 12 | github.com/fsnotify/fsnotify v1.9.0 13 | ) 14 | 15 | require ( 16 | github.com/aquilax/go-perlin v1.1.0 // indirect 17 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 18 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 19 | github.com/charmbracelet/x/ansi v0.11.0 // indirect 20 | github.com/charmbracelet/x/cellbuf v0.0.14 // indirect 21 | github.com/charmbracelet/x/input v0.3.7 // indirect 22 | github.com/charmbracelet/x/term v0.2.2 // indirect 23 | github.com/charmbracelet/x/windows v0.2.2 // indirect 24 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 25 | github.com/clipperhouse/stringish v0.1.1 // indirect 26 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 27 | github.com/dlclark/regexp2 v1.11.5 // indirect 28 | github.com/fogleman/gg v1.3.0 // indirect 29 | github.com/go-logfmt/logfmt v0.6.1 // indirect 30 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect 31 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 32 | github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d // indirect 33 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 34 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 35 | github.com/mattn/go-isatty v0.0.20 // indirect 36 | github.com/mattn/go-runewidth v0.0.19 // indirect 37 | github.com/muesli/cancelreader v0.2.2 // indirect 38 | github.com/muesli/termenv v0.16.0 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/rivo/uniseg v0.4.7 // indirect 41 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb // indirect 42 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 43 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 44 | golang.org/x/image v0.33.0 // indirect 45 | golang.org/x/sys v0.38.0 // indirect 46 | golang.org/x/term v0.37.0 // indirect 47 | golang.org/x/text v0.31.0 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /cli/embed.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // base64 encoded delimiter that should appear at the top 12 | // of a javascript sketch appended to the runal binary 13 | const embedDelimiter = "Ly9ydW5hbDplbWJlZAo=" 14 | 15 | func readEmbeddedSketch() (string, error) { 16 | executable, err := os.Executable() 17 | if err != nil { 18 | return "", err 19 | } 20 | 21 | content, err := os.ReadFile(executable) 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | delimiter, err := base64.StdEncoding.DecodeString(embedDelimiter) 27 | if err != nil { 28 | return "", err 29 | } 30 | 31 | parts := strings.Split(string(content), string(delimiter)) 32 | if len(parts) < 2 { 33 | return "", nil 34 | } 35 | 36 | return parts[len(parts)-1], nil 37 | } 38 | 39 | // createEmbeddedExecutable creates a copy of the current executable 40 | // (named 'outFile') with the contents of 'appendFile' appended to it 41 | func createEmbeddedExecutable(outFilename string, appendFilename string) error { 42 | // read the contents of the appendFile 43 | sketch, err := os.ReadFile(appendFilename) 44 | if err != nil { 45 | return fmt.Errorf("%w reading %s", err, appendFilename) 46 | } 47 | 48 | // create an io.Reader and an io.Writer for io.Copy 49 | execName, err := os.Executable() 50 | if err != nil { 51 | return err 52 | } 53 | execFile, err := os.Open(execName) 54 | if err != nil { 55 | return fmt.Errorf("%w opening %s", err, execName) 56 | } 57 | defer execFile.Close() 58 | 59 | newFile, err := os.Create(outFilename) 60 | if err != nil { 61 | return fmt.Errorf("%w creating %s", err, outFilename) 62 | } 63 | defer newFile.Close() 64 | 65 | _, err = io.Copy(newFile, execFile) 66 | if err != nil { 67 | return fmt.Errorf("%w copying %s to %s", err, execFile.Name(), newFile.Name()) 68 | } 69 | 70 | delimiter, err := base64.StdEncoding.DecodeString(embedDelimiter) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | appStr := fmt.Sprintf("%s\n%s", delimiter, sketch) 76 | _, err = newFile.WriteString(appStr) 77 | if err != nil { 78 | return fmt.Errorf("%w appending sketch to %s", err, newFile.Name()) 79 | } 80 | 81 | // make the permissions on the new executable the same as the old one 82 | srcInfo, err := os.Stat(execName) 83 | if err != nil { 84 | return fmt.Errorf("%w getting permissions for %s", err, execName) 85 | } 86 | err = os.Chmod(outFilename, srcInfo.Mode()) 87 | if err != nil { 88 | return fmt.Errorf("%w setting permissions for %s", err, outFilename) 89 | } 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /stroke.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | // Background sets the background character and colors for the entire canvas. 4 | func (c *Canvas) Background(text, fg, bg string) { 5 | c.BackgroundText(text) 6 | c.BackgroundBg(bg) 7 | c.BackgroundFg(fg) 8 | } 9 | 10 | // BackgroundText sets the character used for the background fill. 11 | func (c *Canvas) BackgroundText(text string) { 12 | c.backgroundIndex = 0 13 | if len(text) == 0 { 14 | c.backgroundText = defaultBackgroundText 15 | return 16 | } 17 | c.backgroundText = text 18 | } 19 | 20 | // BackgroundFg sets the foreground (text) color used by the background fill. 21 | func (c *Canvas) BackgroundFg(fg string) { 22 | c.backgroundFg = color(fg) 23 | } 24 | 25 | // BackgroundBg sets the background color used by the background fill. 26 | func (c *Canvas) BackgroundBg(bg string) { 27 | c.backgroundBg = color(bg) 28 | } 29 | 30 | // Fill sets the fill character and its foreground and background colors. 31 | func (c *Canvas) Fill(text, fg, bg string) { 32 | c.FillText(text) 33 | c.FillBg(bg) 34 | c.FillFg(fg) 35 | } 36 | 37 | // FillText sets the character used for fill operations. 38 | func (c *Canvas) FillText(text string) { 39 | c.fill = true 40 | if len(text) == 0 { 41 | c.fillText = defaultFillText 42 | return 43 | } 44 | c.fillText = text 45 | } 46 | 47 | // FillFg sets the foreground color used for fill operations. 48 | func (c *Canvas) FillFg(fg string) { 49 | c.fill = true 50 | c.fillFg = color(fg) 51 | } 52 | 53 | // FillBg sets the background color used for fill operations. 54 | func (c *Canvas) FillBg(bg string) { 55 | c.fill = true 56 | c.fillBg = color(bg) 57 | } 58 | 59 | // Stroke sets the stroke (outline) character and colors. 60 | func (c *Canvas) Stroke(text, fg, bg string) { 61 | c.StrokeText(text) 62 | c.StrokeBg(bg) 63 | c.StrokeFg(fg) 64 | } 65 | 66 | // StrokeText sets the character used for strokes. 67 | func (c *Canvas) StrokeText(text string) { 68 | c.stroke = true 69 | c.strokeIndex = 0 70 | if len(text) == 0 { 71 | c.strokeText = defaultStrokeText 72 | return 73 | } 74 | c.strokeText = text 75 | } 76 | 77 | // StrokeFg sets the foreground color for strokes. 78 | func (c *Canvas) StrokeFg(fg string) { 79 | c.stroke = true 80 | c.strokeFg = color(fg) 81 | } 82 | 83 | // StrokeBg sets the background color for strokes. 84 | func (c *Canvas) StrokeBg(bg string) { 85 | c.stroke = true 86 | c.strokeBg = color(bg) 87 | } 88 | 89 | // NoStroke disables stroke for subsequent shapes. 90 | func (c *Canvas) NoStroke() { 91 | c.stroke = false 92 | } 93 | 94 | // NoFill disables fill for subsequent shapes. 95 | func (c *Canvas) NoFill() { 96 | c.fill = false 97 | } 98 | -------------------------------------------------------------------------------- /runal.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | // Size sets the dimensions of the canvas. 4 | func (c *Canvas) Size(w, h int) { 5 | c.autoResize = false 6 | if c.cellMode.enabled() { 7 | c.resize(w*2, h) 8 | } else { 9 | c.resize(w, h) 10 | } 11 | } 12 | 13 | // Clear clears the canvas contents. 14 | func (c *Canvas) Clear() { 15 | c.buffer = newFrame(c.Width, c.Height) 16 | } 17 | 18 | // Loop enables continuous redrawing of the canvas. 19 | func (c *Canvas) Loop() { 20 | c.IsLooping = true 21 | c.bus <- newStartEvent() 22 | } 23 | 24 | // NoLoop disables automatic canvas redrawing. 25 | func (c *Canvas) NoLoop() { 26 | c.IsLooping = false 27 | c.bus <- newStopEvent() 28 | } 29 | 30 | // Redraw triggers a manual rendering pass. 31 | func (c *Canvas) Redraw() { 32 | c.bus <- newRenderEvent() 33 | } 34 | 35 | // DisableRendering disables all rendering updates. 36 | // Used when an error is rendered. 37 | func (c *Canvas) DisableRendering() { 38 | c.disabled = true 39 | c.NoLoop() 40 | } 41 | 42 | // CellModeCustom sets a specific character used for cell spacing between elements. 43 | func (c *Canvas) CellModeCustom(char string) { 44 | prev := c.cellMode 45 | c.cellMode = cellModeCustom 46 | c.cellModeRune = []rune(char)[0] 47 | 48 | if prev.enabled() { 49 | c.resize(c.Width*2, c.Height) 50 | } else { 51 | c.resize(c.Width, c.Height) 52 | } 53 | } 54 | 55 | // CellModeDouble makes every cell duplicated. 56 | func (c *Canvas) CellModeDouble() { 57 | prev := c.cellMode 58 | c.cellMode = cellModeDouble 59 | if prev.enabled() { 60 | c.resize(c.Width*2, c.Height) 61 | } else { 62 | c.resize(c.Width, c.Height) 63 | } 64 | } 65 | 66 | // CellModeDefault disables cell mode. 67 | func (c *Canvas) CellModeDefault() { 68 | prev := c.cellMode 69 | c.cellMode = cellModeDisabled 70 | c.cellModeRune = 0 71 | if prev.enabled() { 72 | c.resize(c.Width*2, c.Height) 73 | } else { 74 | c.resize(c.Width, c.Height) 75 | } 76 | } 77 | 78 | // DEPRECATED: Use CellModeCustom() instead. 79 | func (c *Canvas) CellPadding(char string) { 80 | c.CellModeCustom(char) 81 | } 82 | 83 | // DEPRECATED: Use CellModeDouble() instead. 84 | func (c *Canvas) CellPaddingDouble(char string) { 85 | c.CellModeDouble() 86 | } 87 | 88 | // DEPRECATED: Use CellModeDefault() instead. 89 | func (c *Canvas) NoCellPadding() { 90 | c.CellModeDefault() 91 | } 92 | 93 | // Fps sets the rendering framerate in frames per second. 94 | func (c *Canvas) Fps(fps int) { 95 | c.fps = fps 96 | c.bus <- newFPSEvent(fps) 97 | } 98 | 99 | // Exit ends the program execution. 100 | func (c *Canvas) Exit() { 101 | c.bus <- newExitEvent() 102 | } 103 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | tags: ["v*"] 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | os: [ubuntu-22.04, macos-13, windows-2022] 16 | runs-on: ${{ matrix.os }} 17 | env: 18 | CGO_ENABLED: 0 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install Alsa headers 23 | run: sudo apt-get install libasound2-dev 24 | if: startsWith(matrix.os, 'ubuntu') 25 | 26 | - name: Install Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: 1.24 30 | 31 | - name: Build linux|mac 32 | run: go build -C cli -ldflags="-w -s -X main.version=${{ github.ref_name }}" -o ../bin/runal && chmod +x bin/runal 33 | if: ${{ !startsWith(matrix.os, 'windows') }} 34 | 35 | - name: Build windows 36 | run: go build -C cli -ldflags="-w -s -X main.version=${{ github.ref_name }}" -o ../bin/runal.exe 37 | if: startsWith(matrix.os, 'windows') 38 | 39 | - name: Tar.gz linux|mac files 40 | run: tar -zcvf runal_${{ github.ref_name }}_${{ runner.os}}.tar.gz LICENSE -C bin runal 41 | if: ${{ !startsWith(matrix.os, 'windows') }} 42 | 43 | - name: Zip windows files 44 | shell: pwsh 45 | run: | 46 | Compress-Archive bin\runal.exe runal_${{ github.ref_name }}_${{ runner.os}}.zip 47 | if: ${{ startsWith(matrix.os, 'windows') }} 48 | 49 | - name: Upload linux|mac artifact 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: runal_${{ github.sha }}_${{ runner.os}} 53 | path: runal_${{ github.ref_name }}_${{ runner.os}}.tar.gz 54 | if-no-files-found: error 55 | if: ${{ !startsWith(matrix.os, 'windows') }} 56 | 57 | - name: Upload windows artifact 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: runal_${{ github.sha }}_${{ runner.os}} 61 | path: runal_${{ github.ref_name }}_${{ runner.os}}.zip 62 | if-no-files-found: error 63 | if: ${{ startsWith(matrix.os, 'windows') }} 64 | 65 | release: 66 | needs: build 67 | if: startsWith(github.ref, 'refs/tags/v') 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/download-artifact@v4.1.7 71 | with: 72 | name: runal_${{ github.sha }}_macOS 73 | 74 | - uses: actions/download-artifact@v4.1.7 75 | with: 76 | name: runal_${{ github.sha }}_Linux 77 | 78 | - uses: actions/download-artifact@v4.1.7 79 | with: 80 | name: runal_${{ github.sha }}_Windows 81 | 82 | - name: Create release 83 | uses: softprops/action-gh-release@v1 84 | with: 85 | files: | 86 | runal_*.tar.gz 87 | runal_*.zip 88 | -------------------------------------------------------------------------------- /image.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "image" 5 | col "image/color" 6 | "image/jpeg" 7 | "image/png" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/charmbracelet/log" 12 | "github.com/charmbracelet/x/ansi" 13 | "golang.org/x/image/webp" 14 | 15 | "github.com/emprcl/runal/pkg/mosaic" 16 | ) 17 | 18 | type Image interface { 19 | Cell(x, y int) Cell 20 | writable 21 | } 22 | 23 | type writable interface { 24 | write(c *Canvas, x, y, w, h int) 25 | } 26 | 27 | type imageFile struct { 28 | file image.Image 29 | frame frame 30 | } 31 | 32 | func (i *imageFile) Cell(x, y int) Cell { 33 | if i.frame.outOfBounds(x, y) { 34 | return Cell{} 35 | } 36 | return i.frame[y][x].public() 37 | } 38 | 39 | func (i *imageFile) write(c *Canvas, x, y, w, h int) { 40 | m := mosaic.New().Symbol(mosaic.Half) 41 | if w > 0 { 42 | m = m.Width(w) 43 | } 44 | 45 | if h > 0 { 46 | m = m.Height(h) 47 | } 48 | imageBuffer := m.RenderCells(i.file) 49 | i.frame = newFrame(w, h) 50 | c.toggleFill() 51 | for iy := range imageBuffer { 52 | for ix := range imageBuffer[iy] { 53 | if c.outOfBounds(x+ix, y+iy) { 54 | continue 55 | } 56 | cell := cell{ 57 | char: imageBuffer[iy][ix].Char, 58 | background: colorFromImage(imageBuffer[iy][ix].Background), 59 | foreground: colorFromImage(imageBuffer[iy][ix].Foreground), 60 | } 61 | c.write(cell, x+ix, y+iy, 2) 62 | i.frame[iy][ix] = cell 63 | } 64 | } 65 | c.toggleFill() 66 | } 67 | 68 | type imageFrame struct { 69 | frame frame 70 | } 71 | 72 | func (i *imageFrame) Cell(x, y int) Cell { 73 | if i.frame.outOfBounds(x, y) { 74 | return Cell{} 75 | } 76 | return i.frame[y][x].public() 77 | } 78 | 79 | func (i *imageFrame) write(c *Canvas, x, y, w, h int) { 80 | if h == 0 { 81 | _, h = i.frame.size() 82 | } 83 | if w == 0 { 84 | w, _ = i.frame.size() 85 | } 86 | for iy := range i.frame { 87 | for ix := range i.frame[iy] { 88 | if c.outOfBounds(x+ix, y+iy) || 89 | x+ix >= x+w || y+iy >= y+h { 90 | continue 91 | } 92 | c.write(i.frame[iy][ix], x+ix, y+iy, 1) 93 | } 94 | } 95 | } 96 | 97 | func (c *Canvas) LoadImage(path string) Image { 98 | f, err := os.Open(path) 99 | if err != nil { 100 | log.Errorf("can't load image: %v", err) 101 | c.DisableRendering() 102 | return nil 103 | } 104 | defer f.Close() 105 | var img image.Image 106 | switch filepath.Ext(path) { 107 | case ".jpg": 108 | img, err = jpeg.Decode(f) 109 | case ".png": 110 | img, err = png.Decode(f) 111 | case ".webp": 112 | img, err = webp.Decode(f) 113 | } 114 | 115 | if err != nil { 116 | log.Errorf("can't load image: %v", err) 117 | c.DisableRendering() 118 | return nil 119 | } 120 | 121 | return &imageFile{ 122 | file: img, 123 | } 124 | } 125 | 126 | func (c *Canvas) Image(img Image, x, y, w, h int) { 127 | if img == nil { 128 | log.Errorf("can't load empty image") 129 | c.DisableRendering() 130 | return 131 | } 132 | img.write(c, x, y, w, h) 133 | } 134 | 135 | func colorFromImage(c col.Color) ansi.Color { 136 | rgba := col.RGBAModel.Convert(c).(col.RGBA) 137 | return ansi.RGBColor{ 138 | R: rgba.R, 139 | G: rgba.G, 140 | B: rgba.B, 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /noise.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | perlin "github.com/aquilax/go-perlin" 8 | ) 9 | 10 | const ( 11 | alpha = 2. 12 | beta = 2. 13 | n = 3 14 | ) 15 | 16 | func newNoise() *perlin.Perlin { 17 | return perlin.NewPerlin(alpha, beta, n, time.Now().UnixNano()) 18 | } 19 | 20 | // NoiseSeed sets the random seed for noise generation. 21 | func (c *Canvas) NoiseSeed(seed int64) { 22 | c.noise = perlin.NewPerlin(alpha, beta, n, seed) 23 | } 24 | 25 | // Noise1D generates 1D Perlin noise (range [0, 1]) for a given input. 26 | func (c *Canvas) Noise1D(x float64) float64 { 27 | return c.noise.Noise1D(x)/2 + 0.5 28 | } 29 | 30 | // Noise2D generates 2D Perlin noise (range [0, 1]) for a given (x, y) coordinate. 31 | func (c *Canvas) Noise2D(x, y float64) float64 { 32 | return c.noise.Noise2D(x, y)/2 + 0.5 33 | } 34 | 35 | // LoopAngle returns the angular progress (in radians, range [0, 2π]) through a looping cycle 36 | // of given duration in seconds. 37 | func (c *Canvas) LoopAngle(duration int) float64 { 38 | totalFrames := c.fps * duration 39 | frame := c.Framecount % totalFrames 40 | return c.Map(float64(frame), 0, float64(totalFrames), 0, 2*math.Pi) 41 | } 42 | 43 | // NoiseLoop returns a noise value by sampling the noise on a circular 44 | // path of the given radius. 45 | // This is useful for creating cyclic animations or evolving patterns 46 | // that repeat perfectly after one full loop. 47 | // 48 | // Parameters: 49 | // - angle: the loop angle in radians, from 0 to 2π. 50 | // - radius: the radius of the circular path in noise space. 51 | // the higher the radius, the more instable it gets. 52 | // 53 | // Returns: 54 | // - A float64 noise value in the range [0, 1]. 55 | func (c *Canvas) NoiseLoop(angle, radius float64) float64 { 56 | x := c.Map(math.Cos(angle), -1, 1, 0, radius) 57 | y := c.Map(math.Sin(angle), -1, 1, 0, radius) 58 | return c.Noise2D(x, y) 59 | } 60 | 61 | // NoiseLoop1D returns a 1D noise value that loops as the angle progresses. 62 | // It samples a 2D noise space using the given radius and combines it with a horizontal offset. 63 | // 64 | // Parameters: 65 | // - angle: the loop angle in radians, typically from 0 to 2π. 66 | // - radius: the radius of the circular path in noise space. 67 | // - x: horizontal position to offset the noise sampling. 68 | // 69 | // Returns: 70 | // - A float64 noise value in the range [0, 1]. 71 | func (c *Canvas) NoiseLoop1D(angle, radius float64, x int) float64 { 72 | nx := c.Map(math.Cos(angle), -1, 1, 0, radius) 73 | ny := c.Map(math.Sin(angle), -1, 1, 0, radius) 74 | return c.Noise2D(nx+float64(x), ny) 75 | } 76 | 77 | // NoiseLoop2D returns a 2D noise value that loops as the angle progresses. 78 | // It samples a circular path in 2D noise space, offset by the (x, y) coordinates. 79 | // 80 | // Parameters: 81 | // - angle: the loop angle in radians, typically from 0 to 2π. 82 | // - radius: the radius of the circular path in noise space. 83 | // - x, y: coordinates used to offset the sampled position in the noise field. 84 | // 85 | // Returns: 86 | // - A float64 noise value in the range [0, 1]. 87 | func (c *Canvas) NoiseLoop2D(angle, radius float64, x, y int) float64 { 88 | nx := c.Map(math.Cos(angle), -1, 1, 0, radius) 89 | ny := c.Map(math.Sin(angle), -1, 1, 0, radius) 90 | return c.Noise2D(nx+float64(x), ny+float64(y)) 91 | } 92 | -------------------------------------------------------------------------------- /examples/strokefill/strokefill.js: -------------------------------------------------------------------------------- 1 | let xList = [1, 21, 31, 41]; 2 | 3 | function setup(c) {} 4 | 5 | function draw(c) { 6 | c.clear(); 7 | 8 | // rects 9 | 10 | c.stroke("1234567890", "#fffb00", "#0004ff"); 11 | c.fill("1234567890", "#0004ff", "#fffb00"); 12 | c.rect(1, 1, 11, 5); 13 | c.stroke("1234567890", "255", "#ee0000"); 14 | c.text("RECT", 5, 2); 15 | c.text("Stroke: YES", 5, 3); 16 | c.text("Fill: YES", 5, 4); 17 | 18 | c.stroke("1234567890", "#fffb00", "#0004ff"); 19 | c.noFill(); 20 | c.rect(21, 1, 11, 5); 21 | c.stroke("1234567890", "255", "#ee0000"); 22 | c.text("RECT", 25, 2); 23 | c.text("Stroke: YES", 25, 3); 24 | c.text("Fill: NO ", 25, 4); 25 | 26 | c.fill("1234567890", "#0004ff", "#fffb00"); 27 | c.noStroke(); 28 | c.rect(41, 1, 11, 5); 29 | c.stroke("1234567890", "255", "#ee0000"); 30 | c.text("RECT", 45, 2); 31 | c.text("Stroke: NO ", 45, 3); 32 | c.text("Fill: YES", 45, 4); 33 | 34 | // circles 35 | 36 | c.stroke("1234567890", "#fffb00", "#0004ff"); 37 | c.fill("1234567890", "#0004ff", "#fffb00"); 38 | c.circle(7, 13, 5); 39 | c.stroke("1234567890", "255", "#ee0000"); 40 | c.text("CIRCLE", 5, 12); 41 | c.text("Stroke: YES", 5, 13); 42 | c.text("Fill: YES", 5, 14); 43 | 44 | c.stroke("1234567890", "#fffb00", "#0004ff"); 45 | c.noFill(); 46 | c.circle(27, 13, 5); 47 | c.stroke("1234567890", "255", "#ee0000"); 48 | c.text("CIRCLE", 25, 12); 49 | c.text("Stroke: YES", 25, 13); 50 | c.text("Fill: NO ", 25, 14); 51 | 52 | c.fill("1234567890", "#0004ff", "#fffb00"); 53 | c.noStroke(); 54 | c.circle(47, 13, 5); 55 | c.stroke("1234567890", "255", "#ee0000"); 56 | c.text("CIRCLE", 45, 12); 57 | c.text("Stroke: NO ", 45, 13); 58 | c.text("Fill: YES", 45, 14); 59 | 60 | // ellipses 61 | 62 | c.stroke("1234567890", "#fffb00", "#0004ff"); 63 | c.fill("1234567890", "#0004ff", "#fffb00"); 64 | c.ellipse(7, 25, 5, 5); 65 | c.stroke("1234567890", "255", "#ee0000"); 66 | c.text("ELLIPSE", 5, 24); 67 | c.text("Stroke: YES", 5, 25); 68 | c.text("Fill: YES", 5, 26); 69 | 70 | c.stroke("1234567890", "#fffb00", "#0004ff"); 71 | c.noFill(); 72 | c.ellipse(27, 25, 5, 5); 73 | c.stroke("1234567890", "255", "#ee0000"); 74 | c.text("ELLIPSE", 25, 24); 75 | c.text("Stroke: YES", 25, 25); 76 | c.text("Fill: NO ", 25, 26); 77 | 78 | c.fill("1234567890", "#0004ff", "#fffb00"); 79 | c.noStroke(); 80 | c.ellipse(47, 25, 5, 5); 81 | c.stroke("1234567890", "255", "#ee0000"); 82 | c.text("ELLIPSE", 45, 24); 83 | c.text("Stroke: NO ", 45, 25); 84 | c.text("Fill: YES", 45, 26); 85 | 86 | // quads 87 | 88 | c.stroke("1234567890", "#fffb00", "#0004ff"); 89 | c.fill("1234567890", "#0004ff", "#fffb00"); 90 | c.quad(64, 1, 74, 1, 74, 6, 64, 6); 91 | c.stroke("1234567890", "255", "#ee0000"); 92 | c.text("QUAD", 70, 2); 93 | c.text("Stroke: YES", 70, 3); 94 | c.text("Fill: YES", 70, 4); 95 | 96 | c.stroke("1234567890", "#fffb00", "#0004ff"); 97 | c.noFill(); 98 | c.quad(84, 1, 94, 1, 94, 6, 84, 6); 99 | c.stroke("1234567890", "255", "#ee0000"); 100 | c.text("QUAD", 88, 2); 101 | c.text("Stroke: YES", 88, 3); 102 | c.text("Fill: NO ", 88, 4); 103 | 104 | c.fill("1234567890", "#0004ff", "#fffb00"); 105 | c.noStroke(); 106 | c.quad(104, 1, 114, 1, 114, 6, 104, 6); 107 | c.stroke("1234567890", "255", "#ee0000"); 108 | c.text("QUAD", 108, 2); 109 | c.text("Stroke: NO ", 108, 3); 110 | c.text("Fill: YES", 108, 4); 111 | 112 | // triangles 113 | 114 | c.stroke("1234567890", "#fffb00", "#0004ff"); 115 | c.fill("1234567890", "#0004ff", "#fffb00"); 116 | c.triangle(64, 9, 74, 9, 74, 18); 117 | 118 | c.stroke("1234567890", "#fffb00", "#0004ff"); 119 | c.noFill(); 120 | c.triangle(84, 9, 94, 9, 94, 18); 121 | 122 | c.fill("1234567890", "#0004ff", "#fffb00"); 123 | c.noStroke(); 124 | c.triangle(104, 9, 114, 9, 114, 18); 125 | 126 | // line 127 | 128 | c.stroke("1234567890", "#fffb00", "#0004ff"); 129 | c.fill("1234567890", "#0004ff", "#fffb00"); 130 | c.line(64, 25, 74, 25); 131 | 132 | c.stroke("1234567890", "#fffb00", "#0004ff"); 133 | c.noFill(); 134 | c.line(84, 25, 94, 25); 135 | 136 | c.fill("1234567890", "#0004ff", "#fffb00"); 137 | c.noStroke(); 138 | c.line(104, 25, 114, 25); 139 | } 140 | -------------------------------------------------------------------------------- /examples/strokefill/strokefill.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/emprcl/runal" 7 | ) 8 | 9 | var xList = []int{1, 21, 31, 41} 10 | 11 | func main() { 12 | 13 | runal.Run(context.Background(), setup, draw) 14 | } 15 | 16 | func setup(c *runal.Canvas) {} 17 | 18 | func draw(c *runal.Canvas) { 19 | c.Clear() 20 | 21 | // rects 22 | 23 | c.Stroke("1234567890", "#fffb00", "#0004ff") 24 | c.Fill("1234567890", "#0004ff", "#fffb00") 25 | c.Rect(1, 1, 11, 5) 26 | c.Stroke("1234567890", "255", "#ee0000") 27 | c.Text("RECT", 5, 2) 28 | c.Text("Stroke: YES", 5, 3) 29 | c.Text("Fill: YES", 5, 4) 30 | 31 | c.Stroke("1234567890", "#fffb00", "#0004ff") 32 | c.NoFill() 33 | c.Rect(21, 1, 11, 5) 34 | c.Stroke("1234567890", "255", "#ee0000") 35 | c.Text("RECT", 25, 2) 36 | c.Text("Stroke: YES", 25, 3) 37 | c.Text("Fill: NO ", 25, 4) 38 | 39 | c.Fill("1234567890", "#0004ff", "#fffb00") 40 | c.NoStroke() 41 | c.Rect(41, 1, 11, 5) 42 | c.Stroke("1234567890", "255", "#ee0000") 43 | c.Text("RECT", 45, 2) 44 | c.Text("Stroke: NO ", 45, 3) 45 | c.Text("Fill: YES", 45, 4) 46 | 47 | // circles 48 | 49 | c.Stroke("1234567890", "#fffb00", "#0004ff") 50 | c.Fill("1234567890", "#0004ff", "#fffb00") 51 | c.Circle(7, 13, 5) 52 | c.Stroke("1234567890", "255", "#ee0000") 53 | c.Text("CIRCLE", 5, 12) 54 | c.Text("Stroke: YES", 5, 13) 55 | c.Text("Fill: YES", 5, 14) 56 | 57 | c.Stroke("1234567890", "#fffb00", "#0004ff") 58 | c.NoFill() 59 | c.Circle(27, 13, 5) 60 | c.Stroke("1234567890", "255", "#ee0000") 61 | c.Text("CIRCLE", 25, 12) 62 | c.Text("Stroke: YES", 25, 13) 63 | c.Text("Fill: NO ", 25, 14) 64 | 65 | c.Fill("1234567890", "#0004ff", "#fffb00") 66 | c.NoStroke() 67 | c.Circle(47, 13, 5) 68 | c.Stroke("1234567890", "255", "#ee0000") 69 | c.Text("CIRCLE", 45, 12) 70 | c.Text("Stroke: NO ", 45, 13) 71 | c.Text("Fill: YES", 45, 14) 72 | 73 | // ellipses 74 | 75 | c.Stroke("1234567890", "#fffb00", "#0004ff") 76 | c.Fill("1234567890", "#0004ff", "#fffb00") 77 | c.Ellipse(7, 25, 5, 5) 78 | c.Stroke("1234567890", "255", "#ee0000") 79 | c.Text("ELLIPSE", 5, 24) 80 | c.Text("Stroke: YES", 5, 25) 81 | c.Text("Fill: YES", 5, 26) 82 | 83 | c.Stroke("1234567890", "#fffb00", "#0004ff") 84 | c.NoFill() 85 | c.Ellipse(27, 25, 5, 5) 86 | c.Stroke("1234567890", "255", "#ee0000") 87 | c.Text("ELLIPSE", 25, 24) 88 | c.Text("Stroke: YES", 25, 25) 89 | c.Text("Fill: NO ", 25, 26) 90 | 91 | c.Fill("1234567890", "#0004ff", "#fffb00") 92 | c.NoStroke() 93 | c.Ellipse(47, 25, 5, 5) 94 | c.Stroke("1234567890", "255", "#ee0000") 95 | c.Text("ELLIPSE", 45, 24) 96 | c.Text("Stroke: NO ", 45, 25) 97 | c.Text("Fill: YES", 45, 26) 98 | 99 | // quads 100 | 101 | c.Stroke("1234567890", "#fffb00", "#0004ff") 102 | c.Fill("1234567890", "#0004ff", "#fffb00") 103 | c.Quad(64, 1, 74, 1, 74, 6, 64, 6) 104 | c.Stroke("1234567890", "255", "#ee0000") 105 | c.Text("QUAD", 70, 2) 106 | c.Text("Stroke: YES", 70, 3) 107 | c.Text("Fill: YES", 70, 4) 108 | 109 | c.Stroke("1234567890", "#fffb00", "#0004ff") 110 | c.NoFill() 111 | c.Quad(84, 1, 94, 1, 94, 6, 84, 6) 112 | c.Stroke("1234567890", "255", "#ee0000") 113 | c.Text("QUAD", 88, 2) 114 | c.Text("Stroke: YES", 88, 3) 115 | c.Text("Fill: NO ", 88, 4) 116 | 117 | c.Fill("1234567890", "#0004ff", "#fffb00") 118 | c.NoStroke() 119 | c.Quad(104, 1, 114, 1, 114, 6, 104, 6) 120 | c.Stroke("1234567890", "255", "#ee0000") 121 | c.Text("QUAD", 108, 2) 122 | c.Text("Stroke: NO ", 108, 3) 123 | c.Text("Fill: YES", 108, 4) 124 | 125 | // triangles 126 | 127 | c.Stroke("1234567890", "#fffb00", "#0004ff") 128 | c.Fill("1234567890", "#0004ff", "#fffb00") 129 | c.Triangle(64, 9, 74, 9, 74, 18) 130 | 131 | c.Stroke("1234567890", "#fffb00", "#0004ff") 132 | c.NoFill() 133 | c.Triangle(84, 9, 94, 9, 94, 18) 134 | 135 | c.Fill("1234567890", "#0004ff", "#fffb00") 136 | c.NoStroke() 137 | c.Triangle(104, 9, 114, 9, 114, 18) 138 | 139 | // line 140 | 141 | c.Stroke("1234567890", "#fffb00", "#0004ff") 142 | c.Fill("1234567890", "#0004ff", "#fffb00") 143 | c.Line(64, 25, 74, 25) 144 | 145 | c.Stroke("1234567890", "#fffb00", "#0004ff") 146 | c.NoFill() 147 | c.Line(84, 25, 94, 25) 148 | 149 | c.Fill("1234567890", "#0004ff", "#fffb00") 150 | c.NoStroke() 151 | c.Line(104, 25, 114, 25) 152 | 153 | } 154 | -------------------------------------------------------------------------------- /cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "runtime/debug" 10 | 11 | "github.com/charmbracelet/lipgloss" 12 | "github.com/charmbracelet/log" 13 | 14 | "github.com/fsnotify/fsnotify" 15 | 16 | "runal-cli/runtime" 17 | ) 18 | 19 | //go:embed demo.js 20 | var Demo string 21 | 22 | var version string 23 | 24 | const ( 25 | logo = ` 26 | ___ ___ ___ ___ ___ 27 | /\ \ /\__\ /\__\ /\ \ /\__\ 28 | /::\ \ /:/ / /::| | /::\ \ /:/ / 29 | /:/\:\ \ /:/ / /:|:| | /:/\:\ \ /:/ / 30 | /::\~\:\ \ /:/ / ___ /:/|:| |__ /::\~\:\ \ /:/ / 31 | /:/\:\ \:\__\ /:/__/ /\__\ /:/ |:| /\__\ /:/\:\ \:\__\ /:/__/ 32 | \/_|::\/:/ / \:\ \ /:/ / \/__|:|/:/ / \/__\:\/:/ / \:\ \ 33 | |:|::/ / \:\ /:/ / |:/:/ / \::/ / \:\ \ 34 | |:|\/__/ \:\/:/ / |::/ / /:/ / \:\ \ 35 | |:| | \::/ / /:/ / /:/ / \:\__\ 36 | \|__| \/__/ \/__/ \/__/ \/__/` 37 | ) 38 | 39 | func main() { 40 | infile := flag.String("f", "", "sketch file (.js)") 41 | outfile := flag.String("o", "", "output executable file") 42 | demo := flag.Bool("demo", false, "demo mode") 43 | flag.Parse() 44 | 45 | embedded, err := readEmbeddedSketch() 46 | if err != nil { 47 | log.Fatalf("%v reading embedded sketch", err) 48 | return 49 | } 50 | if embedded != "" { 51 | r := runtime.New("", nil) 52 | r.RunInternal(embedded) 53 | return 54 | } 55 | 56 | if *demo { 57 | r := runtime.New("", nil) 58 | r.RunInternal(Demo) 59 | return 60 | } 61 | 62 | if *infile == "" { 63 | displayHelp() 64 | return 65 | } 66 | 67 | if *outfile != "" { 68 | err := createEmbeddedExecutable(*outfile, *infile) 69 | if err != nil { 70 | log.Fatalf("%v creating the embedded executable", err) 71 | } 72 | return 73 | } 74 | 75 | log.SetOutput(os.Stdout) 76 | 77 | if _, err := os.Stat(*infile); errors.Is(err, os.ErrNotExist) { 78 | log.Fatalf("sketch file %s does not exist", *infile) 79 | } 80 | 81 | watcher, err := fsnotify.NewWatcher() 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | defer watcher.Close() 86 | 87 | r := runtime.New(*infile, watcher) 88 | r.Run() 89 | } 90 | 91 | func getVersion() string { 92 | if version != "" { 93 | return version 94 | } 95 | 96 | buildInfo, ok := debug.ReadBuildInfo() 97 | if !ok { 98 | return "dirty" 99 | } 100 | 101 | if buildInfo.Main.Version == "" { 102 | return "dirty" 103 | } 104 | 105 | return buildInfo.Main.Version 106 | } 107 | 108 | func displayHelp() { 109 | fmt.Println( 110 | lipgloss.JoinVertical( 111 | lipgloss.Left, 112 | lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("201")).Render(logo), 113 | lipgloss.NewStyle().MarginLeft(5).Render( 114 | lipgloss.JoinVertical( 115 | lipgloss.Left, 116 | lipgloss.NewStyle().Foreground(lipgloss.Color("79")).Render(fmt.Sprintf("%s - https://empr.cl/runal/", getVersion())), 117 | lipgloss.NewStyle().MarginTop(2).Bold(true).Foreground(lipgloss.Color("81")).Render("USAGE"), 118 | lipgloss.NewStyle().MarginLeft(2).Render( 119 | lipgloss.JoinVertical( 120 | lipgloss.Left, 121 | lipgloss.JoinHorizontal( 122 | lipgloss.Left, 123 | lipgloss.NewStyle().Width(15).Render("-f [FILE]"), 124 | lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("the javascript sketch file (.js) to watch"), 125 | ), 126 | lipgloss.JoinHorizontal( 127 | lipgloss.Left, 128 | lipgloss.NewStyle().Width(15).Render("-demo"), 129 | lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("demo sketch (press space to reseed, c to capture png)"), 130 | ), 131 | lipgloss.JoinHorizontal( 132 | lipgloss.Left, 133 | lipgloss.NewStyle().Width(15).Render("-o [FILE]"), 134 | lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("creates a standalone executable from the -f [FILE]"), 135 | ), 136 | ), 137 | ), 138 | lipgloss.NewStyle().MarginTop(2).Bold(true).Foreground(lipgloss.Color("81")).Render("EXAMPLE"), 139 | lipgloss.NewStyle().MarginLeft(2).MarginBottom(2).Render( 140 | lipgloss.JoinVertical( 141 | lipgloss.Left, 142 | "runal -f my_sketch.js", 143 | "runal -demo", 144 | "runal -f my_sketch.js -o my_executable", 145 | ), 146 | ), 147 | ), 148 | ), 149 | ), 150 | ) 151 | } 152 | -------------------------------------------------------------------------------- /capture.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color/palette" 7 | "image/draw" 8 | "image/gif" 9 | "image/png" 10 | "os" 11 | 12 | "github.com/charmbracelet/log" 13 | 14 | ansitoimage "github.com/xaviergodart/go-ansi-to-image" 15 | ) 16 | 17 | // SaveCanvasToPNG exports the canvas to a png image file. 18 | func (c *Canvas) SaveCanvasToPNG(filename string) { 19 | c.save = true 20 | c.saveFilename = filename 21 | c.Redraw() 22 | } 23 | 24 | // SaveCanvasToGIF exports the canvas to an animated gif 25 | // for a given duration (in seconds). 26 | func (c *Canvas) SaveCanvasToGIF(filename string, duration int) { 27 | if c.frames != nil { 28 | return 29 | } 30 | c.videoFormat = videoFormatGif 31 | totalFrames := duration * c.fps 32 | c.frames = make([]image.Image, 0, totalFrames) 33 | c.saveFilename = filename 34 | } 35 | 36 | // SaveCanvasToMP4 exports the canvas to a mp4 (h264) video 37 | // for a given duration (in seconds). 38 | // Depends on ffmpeg. 39 | func (c *Canvas) SaveCanvasToMP4(filename string, duration int) { 40 | if !checkFFMPEG() { 41 | log.Error("Can't use SaveCanvasToMP4(). ffmpeg is not installed.") 42 | c.DisableRendering() 43 | } 44 | if c.frames != nil { 45 | return 46 | } 47 | c.videoFormat = videoFormatMp4 48 | totalFrames := duration * c.fps 49 | c.frames = make([]image.Image, 0, totalFrames) 50 | c.saveFilename = filename 51 | } 52 | 53 | // SavedCanvasFont sets a custom font (tff) file used for rendering text characters 54 | // in exported images generated via SaveCanvasTo...(). 55 | func (c *Canvas) SavedCanvasFont(filename string) { 56 | file, err := os.ReadFile(filename) 57 | if err != nil { 58 | return 59 | } 60 | config := c.capture.Config() 61 | config.MonoRegularFontBytes = file 62 | c.capture, _ = ansitoimage.NewConverter(config) 63 | } 64 | 65 | // SavedCanvasFontSize sets the font size used for rendering text characters 66 | // in exported images generated via SaveCanvas(). 67 | func (c *Canvas) SavedCanvasFontSize(size int) { 68 | config := c.capture.Config() 69 | config.MonoRegularFontPoints = float64(size) 70 | c.capture, _ = ansitoimage.NewConverter(config) 71 | } 72 | 73 | func newCapture(width, height int) *ansitoimage.Converter { 74 | imageCapture, _ := ansitoimage.NewConverter(newCaptureConfig(width, height)) 75 | return imageCapture 76 | } 77 | 78 | func newCaptureConfig(width, height int) ansitoimage.Config { 79 | captureConfig := ansitoimage.DefaultConfig 80 | captureConfig.Padding = 0 81 | captureConfig.PageCols = width - 2 82 | captureConfig.PageRows = height 83 | return captureConfig 84 | } 85 | 86 | func (c *Canvas) captureResize(width, height int) { 87 | config := c.capture.Config() 88 | config.PageCols = width - 2 89 | config.PageRows = height 90 | c.capture, _ = ansitoimage.NewConverter(config) 91 | } 92 | 93 | func (c *Canvas) generateFrame(frame string) { 94 | err := c.capture.Parse(frame) 95 | if err != nil { 96 | log.Fatalf("can't generate frame: %v", err) 97 | } 98 | } 99 | 100 | func (c *Canvas) exportCanvasToPNG(frame string) { 101 | fmt.Println("Saving png...") 102 | c.generateFrame(frame) 103 | img, _ := c.capture.ToPNG() 104 | err := os.WriteFile(c.saveFilename, img, 0o644) 105 | if err != nil { 106 | log.Fatal("can't create png file: %v", err) 107 | } 108 | } 109 | 110 | func (c *Canvas) exportFramesToGIF() error { 111 | outGif := &gif.GIF{} 112 | 113 | delay := 100 / c.fps 114 | 115 | for _, img := range c.frames { 116 | bounds := img.Bounds() 117 | paletted := image.NewPaletted(bounds, palette.Plan9) 118 | draw.FloydSteinberg.Draw(paletted, bounds, img, image.Point{}) 119 | outGif.Image = append(outGif.Image, paletted) 120 | outGif.Delay = append(outGif.Delay, delay) 121 | } 122 | 123 | file, err := os.Create(c.saveFilename) 124 | if err != nil { 125 | return err 126 | } 127 | defer file.Close() 128 | 129 | return gif.EncodeAll(file, outGif) 130 | } 131 | 132 | func (c *Canvas) exportFramesToMP4() error { 133 | dir := randomDir() 134 | defer os.RemoveAll(dir) 135 | 136 | for i, img := range c.frames { 137 | f, err := os.Create(fmt.Sprintf("%s/frame_%d.png", dir, i)) 138 | if err != nil { 139 | log.Fatal("can't create frame file: %v", err) 140 | } 141 | defer f.Close() 142 | 143 | if err := png.Encode(f, img); err != nil { 144 | log.Fatal("can't encode frame: %v", err) 145 | } 146 | f.Close() 147 | } 148 | 149 | return framesToMP4Videos(c.fps, fmt.Sprintf("%s/frame_%%d.png", dir), c.saveFilename) 150 | } 151 | 152 | func (c *Canvas) recordFrame(output string) { 153 | if len(c.frames) >= cap(c.frames) { 154 | var err error 155 | switch c.videoFormat { 156 | case videoFormatGif: 157 | fmt.Println("Saving GIF...") 158 | err = c.exportFramesToGIF() 159 | case videoFormatMp4: 160 | fmt.Println("Saving MP4...") 161 | err = c.exportFramesToMP4() 162 | } 163 | 164 | if err != nil { 165 | log.Fatal("can't export frames: %v", err) 166 | } 167 | c.frames = nil 168 | return 169 | } 170 | c.generateFrame(output) 171 | frame := image.NewRGBA(c.capture.Image().Bounds()) 172 | copy(frame.Pix, c.capture.Image().(*image.RGBA).Pix) 173 | c.frames = append(c.frames, frame) 174 | } 175 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | package runal 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/charmbracelet/log" 9 | "github.com/charmbracelet/x/input" 10 | ) 11 | 12 | const ( 13 | defaultFPS = 30 14 | ) 15 | 16 | type callbacks struct { 17 | onKey func(c *Canvas, e KeyEvent) 18 | onMouseMove func(c *Canvas, e MouseEvent) 19 | onMouseClick func(c *Canvas, e MouseEvent) 20 | onMouseRelease func(c *Canvas, e MouseEvent) 21 | onMouseWheel func(c *Canvas, e MouseEvent) 22 | } 23 | 24 | type callbackOption func(*callbacks) 25 | 26 | func WithOnKey(onKey func(c *Canvas, e KeyEvent)) callbackOption { 27 | return func(c *callbacks) { 28 | c.onKey = onKey 29 | } 30 | } 31 | 32 | func WithOnMouseMove(onMouseMove func(c *Canvas, e MouseEvent)) callbackOption { 33 | return func(c *callbacks) { 34 | c.onMouseMove = onMouseMove 35 | } 36 | } 37 | 38 | func WithOnMouseClick(onMouseClick func(c *Canvas, e MouseEvent)) callbackOption { 39 | return func(c *callbacks) { 40 | c.onMouseClick = onMouseClick 41 | } 42 | } 43 | 44 | func WithOnMouseRelease(onMouseRelease func(c *Canvas, e MouseEvent)) callbackOption { 45 | return func(c *callbacks) { 46 | c.onMouseRelease = onMouseRelease 47 | } 48 | } 49 | 50 | func WithOnMouseWheel(onMouseWheel func(c *Canvas, e MouseEvent)) callbackOption { 51 | return func(c *callbacks) { 52 | c.onMouseWheel = onMouseWheel 53 | } 54 | } 55 | 56 | func Run(ctx context.Context, setup, draw func(c *Canvas), opts ...callbackOption) { 57 | Start(ctx, nil, setup, draw, opts...).Wait() 58 | } 59 | 60 | func Start(ctx context.Context, done chan struct{}, setup, draw func(c *Canvas), opts ...callbackOption) *sync.WaitGroup { 61 | if setup == nil { 62 | log.Fatal("setup method is required") 63 | } 64 | if draw == nil { 65 | log.Fatal("draw method is required") 66 | } 67 | ctx, cancel := context.WithCancel(ctx) 68 | w, h := termSize() 69 | c := newCanvas(w, h) 70 | wg := sync.WaitGroup{} 71 | 72 | eventCallbacks := callbacks{} 73 | for _, opt := range opts { 74 | opt(&eventCallbacks) 75 | } 76 | 77 | ticker := time.NewTicker(newFramerate(defaultFPS)) 78 | 79 | exit := func() { 80 | ticker.Stop() 81 | resetCursorPosition() 82 | clearScreen() 83 | showCursor() 84 | disableMouse() 85 | } 86 | 87 | defer func() { 88 | if r := recover(); r != nil { 89 | exit() 90 | panic(r) 91 | } 92 | }() 93 | 94 | resize := listenForResize() 95 | inputEvents := listenForInputEvents(ctx, &wg) 96 | 97 | enterAltScreen() 98 | enableMouse() 99 | 100 | setup(c) 101 | render := func() { 102 | resetCursorPosition() 103 | draw(c) 104 | c.render() 105 | } 106 | render() 107 | 108 | wg.Add(1) 109 | go func() { 110 | defer func() { 111 | exit() 112 | wg.Done() 113 | }() 114 | for { 115 | select { 116 | case <-ctx.Done(): 117 | return 118 | case <-resize: 119 | clearScreen() 120 | w, h := termSize() 121 | c.termWidth = w 122 | c.termHeight = h 123 | if c.autoResize { 124 | c.resize(w, h) 125 | } 126 | render() 127 | case event := <-c.bus: 128 | switch event.name { 129 | case "fps": 130 | ticker.Reset(newFramerate(event.value)) 131 | case "stop": 132 | ticker.Stop() 133 | case "start": 134 | ticker.Reset(newFramerate(defaultFPS)) 135 | case "render": 136 | render() 137 | case "exit": 138 | if done != nil { 139 | done <- struct{}{} 140 | } 141 | cancel() 142 | return 143 | } 144 | case event := <-inputEvents: 145 | switch e := event.(type) { 146 | case input.MouseMotionEvent: 147 | c.setMousePostion(e.X, e.Y) 148 | if eventCallbacks.onMouseMove != nil { 149 | eventCallbacks.onMouseMove(c, MouseEvent{ 150 | X: c.MouseX, 151 | Y: c.MouseY, 152 | }) 153 | } 154 | case input.MouseClickEvent: 155 | if eventCallbacks.onMouseClick != nil { 156 | c.setMousePostion(e.X, e.Y) 157 | eventCallbacks.onMouseClick(c, MouseEvent{ 158 | X: c.MouseX, 159 | Y: c.MouseY, 160 | Button: e.Button.String(), 161 | }) 162 | } 163 | case input.MouseReleaseEvent: 164 | if eventCallbacks.onMouseRelease != nil { 165 | c.setMousePostion(e.X, e.Y) 166 | eventCallbacks.onMouseRelease(c, MouseEvent{ 167 | X: c.MouseX, 168 | Y: c.MouseY, 169 | Button: e.Button.String(), 170 | }) 171 | } 172 | case input.MouseWheelEvent: 173 | if eventCallbacks.onMouseWheel != nil { 174 | c.setMousePostion(e.X, e.Y) 175 | eventCallbacks.onMouseWheel(c, MouseEvent{ 176 | X: c.MouseX, 177 | Y: c.MouseY, 178 | Button: e.Button.String(), 179 | }) 180 | } 181 | case input.KeyEvent: 182 | switch e.String() { 183 | case "ctrl+c": 184 | if done != nil { 185 | done <- struct{}{} 186 | } 187 | cancel() 188 | return 189 | default: 190 | if eventCallbacks.onKey != nil { 191 | eventCallbacks.onKey(c, KeyEvent{ 192 | Key: e.Key().String(), 193 | Code: int(e.Key().Code), 194 | }) 195 | } 196 | } 197 | } 198 | case <-ticker.C: 199 | render() 200 | } 201 | } 202 | }() 203 | 204 | return &wg 205 | } 206 | 207 | func newFramerate(fps int) time.Duration { 208 | return time.Second / time.Duration(fps) 209 | } 210 | -------------------------------------------------------------------------------- /cli/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/charmbracelet/log" 11 | 12 | "github.com/dop251/goja" 13 | "github.com/emprcl/runal" 14 | "github.com/fsnotify/fsnotify" 15 | ) 16 | 17 | type console struct { 18 | canvas *runal.Canvas 19 | } 20 | 21 | func (c console) Log(messages ...string) { 22 | v := make([]any, len(messages)) 23 | for i, m := range messages { 24 | v[i] = m 25 | } 26 | c.canvas.Debug(v...) 27 | } 28 | 29 | type runtime struct { 30 | watcher *fsnotify.Watcher 31 | filename string 32 | } 33 | 34 | func New(filename string, watcher *fsnotify.Watcher) runtime { 35 | return runtime{ 36 | watcher: watcher, 37 | filename: filename, 38 | } 39 | } 40 | 41 | func (s runtime) Run() { 42 | done := make(chan struct{}, 1) 43 | ctx, cancel := context.WithCancel(context.Background()) 44 | content, err := os.ReadFile(s.filename) 45 | vm, setup, draw, cb, err := parseJS(string(content)) 46 | var wg *sync.WaitGroup 47 | if err != nil { 48 | log.Error(err) 49 | } else { 50 | wg = s.runSketch(ctx, done, vm, setup, draw, cb) 51 | } 52 | 53 | go func() { 54 | for { 55 | select { 56 | case event, ok := <-s.watcher.Events: 57 | if !ok { 58 | return 59 | } 60 | if event.Has(fsnotify.Write) { 61 | if !strings.HasSuffix(event.Name, s.filename) { 62 | continue 63 | } 64 | cancel() 65 | if wg != nil { 66 | wg.Wait() 67 | } 68 | content, err := os.ReadFile(event.Name) 69 | if err != nil { 70 | log.Error(err) 71 | continue 72 | } 73 | vm, setup, draw, cb, err := parseJS(string(content)) 74 | if err != nil { 75 | log.Error(err) 76 | continue 77 | } 78 | ctx, cancel = context.WithCancel(context.Background()) 79 | wg = s.runSketch(ctx, done, vm, setup, draw, cb) 80 | } 81 | case err, ok := <-s.watcher.Errors: 82 | if !ok { 83 | return 84 | } 85 | log.Error(err) 86 | } 87 | } 88 | }() 89 | 90 | err = s.watcher.Add(filepath.Dir(s.filename)) 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | 95 | <-done 96 | cancel() 97 | wg.Wait() 98 | } 99 | 100 | func (s runtime) RunInternal(sketch string) { 101 | vm, setup, draw, callbacks, err := parseJS(sketch) 102 | if err != nil { 103 | log.Error(err) 104 | return 105 | } 106 | s.runSketch(context.Background(), nil, vm, setup, draw, callbacks).Wait() 107 | } 108 | 109 | func (s runtime) runSketch(ctx context.Context, done chan struct{}, vm *goja.Runtime, setup, draw goja.Callable, cb callbacks) *sync.WaitGroup { 110 | panicRecover := func(c *runal.Canvas) { 111 | if r := recover(); r != nil { 112 | log.Errorf("%v", r) 113 | c.DisableRendering() 114 | } 115 | } 116 | 117 | var onKeyCallback func(c *runal.Canvas, e runal.KeyEvent) 118 | if cb.onKey != nil { 119 | onKeyCallback = func(c *runal.Canvas, e runal.KeyEvent) { 120 | defer panicRecover(c) 121 | _, err := cb.onKey(goja.Undefined(), vm.ToValue(c), vm.ToValue(e)) 122 | if err != nil { 123 | log.Error(err) 124 | c.DisableRendering() 125 | } 126 | } 127 | } 128 | 129 | var onMouseClickCallback func(c *runal.Canvas, e runal.MouseEvent) 130 | if cb.onMouseClick != nil { 131 | onMouseClickCallback = func(c *runal.Canvas, e runal.MouseEvent) { 132 | defer panicRecover(c) 133 | _, err := cb.onMouseClick(goja.Undefined(), vm.ToValue(c), vm.ToValue(e)) 134 | if err != nil { 135 | log.Error(err) 136 | c.DisableRendering() 137 | } 138 | } 139 | } 140 | 141 | var onMouseReleaseCallback func(c *runal.Canvas, e runal.MouseEvent) 142 | if cb.onMouseRelease != nil { 143 | onMouseReleaseCallback = func(c *runal.Canvas, e runal.MouseEvent) { 144 | defer panicRecover(c) 145 | _, err := cb.onMouseRelease(goja.Undefined(), vm.ToValue(c), vm.ToValue(e)) 146 | if err != nil { 147 | log.Error(err) 148 | c.DisableRendering() 149 | } 150 | } 151 | } 152 | 153 | var onMouseWheelCallback func(c *runal.Canvas, e runal.MouseEvent) 154 | if cb.onMouseWheel != nil { 155 | onMouseWheelCallback = func(c *runal.Canvas, e runal.MouseEvent) { 156 | defer panicRecover(c) 157 | _, err := cb.onMouseWheel(goja.Undefined(), vm.ToValue(c), vm.ToValue(e)) 158 | if err != nil { 159 | log.Error(err) 160 | c.DisableRendering() 161 | } 162 | } 163 | } 164 | 165 | var onMouseMoveCallback func(c *runal.Canvas, e runal.MouseEvent) 166 | if cb.onMouseMove != nil { 167 | onMouseMoveCallback = func(c *runal.Canvas, e runal.MouseEvent) { 168 | defer panicRecover(c) 169 | _, err := cb.onMouseMove(goja.Undefined(), vm.ToValue(c), vm.ToValue(e)) 170 | if err != nil { 171 | log.Error(err) 172 | c.DisableRendering() 173 | } 174 | } 175 | } 176 | 177 | return runal.Start( 178 | ctx, 179 | done, 180 | func(c *runal.Canvas) { 181 | defer panicRecover(c) 182 | vm.Set("console", console{ 183 | canvas: c, 184 | }) 185 | _, err := setup(goja.Undefined(), vm.ToValue(c)) 186 | if err != nil { 187 | log.Error(err) 188 | c.DisableRendering() 189 | } 190 | }, 191 | func(c *runal.Canvas) { 192 | defer panicRecover(c) 193 | _, err := draw(goja.Undefined(), vm.ToValue(c)) 194 | if err != nil { 195 | log.Error(err) 196 | c.DisableRendering() 197 | } 198 | }, 199 | runal.WithOnKey(onKeyCallback), 200 | runal.WithOnMouseMove(onMouseMoveCallback), 201 | runal.WithOnMouseClick(onMouseClickCallback), 202 | runal.WithOnMouseRelease(onMouseReleaseCallback), 203 | runal.WithOnMouseWheel(onMouseWheelCallback), 204 | ) 205 | } 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Runal 2 | 3 | ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/emprcl/runal) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/emprcl/runal/build.yml) 4 | 5 | :notebook: **[User Manual](https://empr.cl/runal/)** 6 | 7 | Runal is a text-based creative coding environment for the terminal. It works similarly as [processing](https://processing.org/) or [p5js](https://p5js.org/) but it does all the rendering as text. It can either be programmed with [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), or used as a [Go](https://go.dev/) package. 8 | 9 | **_Runal is a work-in-progress. The API should not be considered as stable until it reaches 1.0._** 10 | 11 | _Feel free to [open an issue](https://github.com/emprcl/runal/issues/new)._ 12 | 13 | ![runal screenshot](/docs/screenshot.png) 14 | 15 | ## Installation 16 | 17 | ### Quick-install 18 | 19 | On **linux** or **macOS**, you can run this quick-install bash script: 20 | ```sh 21 | curl -sSL empr.cl/get/runal | bash 22 | ``` 23 | 24 | ### Packages 25 | 26 | #### AUR 27 | 28 | https://aur.archlinux.org/packages/runal 29 | 30 | ### Manual installation 31 | 32 | #### Linux & macOS 33 | 34 | [Download the last release](https://github.com/emprcl/runal/releases) for your platform. 35 | 36 | Then: 37 | ```sh 38 | # Extract files 39 | mkdir -p runal && tar -zxvf runal_VERSION_PLATFORM.tar.gz -C runal 40 | cd runal 41 | 42 | # Run runal 43 | ./runal 44 | 45 | # Run runal demo 46 | ./runal -demo 47 | ``` 48 | 49 | #### Windows 50 | 51 | > _We recommend using Windows Terminal with a good monospace font like Iosevka to display Runal correctly on Windows._ 52 | 53 | Unzip the last [windows release](https://github.com/emprcl/runal/releases) and, in the same directory, run: 54 | ```winbatch 55 | ; Run runal 56 | .\runal.exe 57 | 58 | ; Run runal demo 59 | .\runal.exe -demo 60 | ``` 61 | 62 | ### Build it yourself 63 | 64 | You'll need [go 1.23](https://go.dev/dl/) minimum. 65 | Although you should be able to build it for either **linux**, **macOS** or **Windows**, it has only been tested on **linux**. 66 | 67 | ```sh 68 | # Linux 69 | make GOLANG_OS=linux build 70 | 71 | # macOS 72 | make GOLANG_OS=darwin build 73 | 74 | # Windows 75 | make GOLANG_OS=windows build 76 | 77 | # Raspberry Pi OS 78 | make GOLANG_OS=linux GOLANG_ARCH=arm64 build 79 | ``` 80 | 81 | 82 | ## Usage 83 | 84 | ### JavaScript runtime 85 | 86 | You can use JavaScript for scripting your sketch. Your js file should contain a `setup` and a `draw` method. Both methods take a single argument (here `c`) representing a canvas object that holds all the available primitives: 87 | ```js 88 | // sketch.js 89 | 90 | function setup(c) {} 91 | 92 | function draw(c) {} 93 | ``` 94 | 95 | You can add extra methods `onKey`, `onMouseMove`, `onMouseClick`, `onMouseRelease` and `onMouseWheel` to catch keyboard and mouse events: 96 | ```js 97 | function onKey(c, e) {} 98 | function onMouseMove(c, e) {} 99 | function onMouseClick(c, e) {} 100 | function onMouseRelease(c, e) {} 101 | function onMouseWheel(c, e) {} 102 | ```` 103 | 104 | And you can then execute the file with: 105 | ```sh 106 | ./runal -f sketch.js 107 | ``` 108 | 109 | The js file will be automatically reloaded when modified, no need to restart the command. 110 | 111 | #### Standalone executable 112 | 113 | You can create a standalone executable from the JavaScript file specified with **-f** using **-o [FILE]**: 114 | ```sh 115 | ./runal -f sketch.js -o sketch 116 | 117 | # Run the standalone executable 118 | ./sketch 119 | ``` 120 | 121 | ### Go package 122 | 123 | Because Runal is written in Go, you can also use it as a Go package. 124 | 125 | ```go 126 | // sketch.go 127 | package main 128 | 129 | import ( 130 | "context" 131 | "os" 132 | "os/signal" 133 | 134 | "github.com/emprcl/runal" 135 | ) 136 | 137 | func main() { 138 | runal.Run(context.Background(), setup, draw, runal.WithOnKey(onKey), runal.WithOnMouseClick(onMouseClick)) 139 | } 140 | 141 | func setup(c *runal.Canvas) {} 142 | 143 | func draw(c *runal.Canvas) {} 144 | 145 | func onKey(c *runal.Canvas, e runal.KeyEvent) {} 146 | func onMouseClick(c *runal.Canvas, e runal.MouseEvent) {} 147 | ``` 148 | 149 | Then, simply build it: 150 | ``` 151 | go run sketch.go 152 | ``` 153 | 154 | ## Documentation 155 | 156 | Check the [API reference](https://empr.cl/runal/#reference). 157 | You can also check some examples in the [examples directory](https://github.com/emprcl/runal/tree/main/examples). 158 | 159 | ## Contributing 160 | 161 | Contributions are very welcome, even if you're a beginner! Whether it's code, documentation, bug reports, examples, or just ideas, you're encouraged to join in. 162 | 163 | Just be kind, inclusive, and patient. We're all here to learn and build something cool together. 164 | 165 | How to contribute: 166 | 1) Start with a [discussion](https://github.com/emprcl/runal/discussions) or [open an issue](https://github.com/emprcl/runal/issues) to report a bug or suggest an enhancement. **Please check if one already exists on the same topic first.** 167 | 2) Open a [Pull Request](https://github.com/emprcl/runal/pulls). Please keep it small and focused. 168 | 169 | You can also contribute by sharing what you've made with Runal on GitHub, social media, or anywhere else. We'd love to see it! 170 | 171 | ## Acknowledgments 172 | 173 | Runal uses a few awesome packages: 174 | - [dop251/goja](https://github.com/dop251/goja) for the JavaScript engine 175 | - [fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) for watching file changes in realtime 176 | - [charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) for handling colors 177 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/bezier/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/circle/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/colors/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/draw/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/glitch/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/grid/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/image/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /examples/line/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= 2 | github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= 6 | github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 10 | github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 11 | github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA= 12 | github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE= 13 | github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= 14 | github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= 15 | github.com/charmbracelet/x/input v0.3.7 h1:UzVbkt1vgM9dBQ+K+uRolBlN6IF2oLchmPKKo/aucXo= 16 | github.com/charmbracelet/x/input v0.3.7/go.mod h1:ZSS9Cia6Cycf2T6ToKIOxeTBTDwl25AGwArJuGaOBH8= 17 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 18 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 19 | github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 20 | github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 21 | github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 22 | github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 23 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 24 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 25 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 26 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 30 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 31 | github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= 32 | github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= 33 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= 38 | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= 39 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 40 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 41 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 42 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 43 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 44 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 45 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 46 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 47 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 48 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 49 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 50 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 57 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb h1:QP/fxPNvogcfegMkLJOTEFrlZ5uq2lJkxG3dT+B3A44= 61 | github.com/xaviergodart/go-ansi-to-image v0.0.0-20250620163834-f2b31334a2bb/go.mod h1:lRuNUc4+uE2pSWAWrs1QsvfkmqnsHR7CDqrdF3naJKA= 62 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 63 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 64 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 65 | golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 66 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 67 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 70 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 72 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | --------------------------------------------------------------------------------