141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package ansii
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"math"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
type ANSI string
|
|
|
|
const (
|
|
reset ANSI = "\033[0m"
|
|
plain ANSI = ""
|
|
bold ANSI = "\033[1m"
|
|
underline ANSI = "\033[4m"
|
|
black ANSI = "\033[30m"
|
|
red ANSI = "\033[31m"
|
|
green ANSI = "\033[32m"
|
|
yellow ANSI = "\033[33m"
|
|
blue ANSI = "\033[34m"
|
|
purple ANSI = "\033[35m"
|
|
cyan ANSI = "\033[36m"
|
|
white ANSI = "\033[37m"
|
|
blackBg ANSI = "\033[40m"
|
|
redBg ANSI = "\033[41m"
|
|
greenBg ANSI = "\033[42m"
|
|
yellowBg ANSI = "\033[43m"
|
|
blueBg ANSI = "\033[44m"
|
|
purpleBg ANSI = "\033[45m"
|
|
cyanBg ANSI = "\033[46m"
|
|
whiteBg ANSI = "\033[47m"
|
|
clearScreen ANSI = "\033[2J"
|
|
hideCursor ANSI = "\033[?25l"
|
|
showCursor ANSI = "\033[?25h"
|
|
)
|
|
|
|
type Offset struct {
|
|
X float32
|
|
Y float32
|
|
}
|
|
|
|
type style struct {
|
|
Reset ANSI
|
|
Plain ANSI
|
|
Bold ANSI
|
|
Underline ANSI
|
|
}
|
|
|
|
type color struct {
|
|
Black ANSI
|
|
Red ANSI
|
|
Green ANSI
|
|
Yellow ANSI
|
|
Blue ANSI
|
|
Purple ANSI
|
|
Cyan ANSI
|
|
White ANSI
|
|
}
|
|
|
|
type screen struct {
|
|
ClearScreen ANSI
|
|
HideCursor ANSI
|
|
ShowCursor ANSI
|
|
}
|
|
|
|
type ascii struct {
|
|
Block string
|
|
}
|
|
|
|
func GetTermSize() (width int, height int) {
|
|
var fd int = int(os.Stdout.Fd())
|
|
width, height, err := term.GetSize(fd)
|
|
if err != nil {
|
|
fmt.Println(string(Screen.ClearScreen) + "Fatal: error getting terminal size.")
|
|
os.Exit(1)
|
|
}
|
|
return width, height
|
|
}
|
|
|
|
func MakeTermRaw() (*term.State, error) {
|
|
var fd int = int(os.Stdout.Fd())
|
|
return term.MakeRaw(fd)
|
|
}
|
|
|
|
func RestoreTerm(prev *term.State) error {
|
|
var fd int = int(os.Stdout.Fd())
|
|
return term.Restore(fd, prev)
|
|
}
|
|
|
|
func (s screen) PlaceCursor(X, Y int) ANSI {
|
|
return ANSI(fmt.Sprintf("\033[%d;%dH", Y, X))
|
|
}
|
|
|
|
var (
|
|
Styles = style{Bold: bold, Underline: underline, Reset: reset, Plain: plain}
|
|
Colors = color{Red: red, Green: green, Yellow: yellow, Blue: blue, Purple: purple, Cyan: cyan, White: white}
|
|
Screen = screen{ClearScreen: clearScreen, HideCursor: hideCursor, ShowCursor: showCursor}
|
|
Blocks = ascii{Block: "█"}
|
|
)
|
|
|
|
// Draws a box of dimensions `height` and `width` at `offset`.
|
|
// The `offset` is the top left cell of the square.
|
|
// Blocks that would be placed off screen are clipped.
|
|
func DrawBox(builder *strings.Builder, offset Offset, height int, width int, style ANSI) {
|
|
builder.WriteString(string(style))
|
|
for hIdx := range height {
|
|
if hIdx == 0 || hIdx == height-1 {
|
|
for wIdx := range width {
|
|
drawPixel(builder, offset.X+float32(wIdx), offset.Y+float32(hIdx))
|
|
}
|
|
} else {
|
|
drawPixel(builder, offset.X, offset.Y+float32(hIdx))
|
|
drawPixel(builder, offset.X+float32(width-1), offset.Y+float32(hIdx))
|
|
}
|
|
}
|
|
builder.WriteString(string(Styles.Reset))
|
|
return
|
|
}
|
|
|
|
func drawPixel(builder *strings.Builder, offsetX, offsetY float32) {
|
|
termWidth, termHeight := GetTermSize()
|
|
|
|
scaledX := math.Floor(float64(offsetX * float32(termWidth)))
|
|
scaledY := math.Floor(float64(offsetY * float32(termHeight)))
|
|
|
|
slog.Debug("positions", slog.Any("x", scaledX), slog.Any("y", scaledY))
|
|
|
|
// TODO: Does float to int convertion cause many problems?
|
|
builder.WriteString(string(Screen.PlaceCursor(int(scaledX), int(scaledY)) + ANSI(Blocks.Block)))
|
|
}
|
|
|
|
func DrawPixelStyle(builder *strings.Builder, offset Offset, style ANSI) {
|
|
builder.WriteString(string(style))
|
|
drawPixel(builder, offset.X, offset.Y)
|
|
builder.WriteString(string(Styles.Reset))
|
|
}
|