152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
// ©AngelaMos | 2026
|
|
// theme.go
|
|
|
|
package tui
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
const (
|
|
colorVoid = "#0B0620"
|
|
colorText = "#E7E0FF"
|
|
colorMuted = "#9A8AD0"
|
|
colorDim = "#574B7D"
|
|
|
|
colorMagenta = "#FF2E97"
|
|
colorAmber = "#FF8A3D"
|
|
colorYellow = "#FFE04D"
|
|
colorCyan = "#00E5FF"
|
|
colorViolet = "#A65CFF"
|
|
colorBlue = "#2D6BFF"
|
|
)
|
|
|
|
const (
|
|
barFull = "█"
|
|
barEmpty = "░"
|
|
ruleLead = "▓▒░"
|
|
ruleBody = "─"
|
|
)
|
|
|
|
func bandColor(b band) string {
|
|
switch b {
|
|
case bandCritical:
|
|
return colorMagenta
|
|
case bandHigh:
|
|
return colorAmber
|
|
case bandMedium:
|
|
return colorYellow
|
|
case bandLow:
|
|
return colorCyan
|
|
default:
|
|
return colorDim
|
|
}
|
|
}
|
|
|
|
type Theme struct {
|
|
Brand lipgloss.Style
|
|
BrandMark lipgloss.Style
|
|
Meta lipgloss.Style
|
|
ColHead lipgloss.Style
|
|
Text lipgloss.Style
|
|
Muted lipgloss.Style
|
|
Dim lipgloss.Style
|
|
Rank lipgloss.Style
|
|
RankSel lipgloss.Style
|
|
Headline lipgloss.Style
|
|
HeadlineSel lipgloss.Style
|
|
SelBar lipgloss.Style
|
|
KeyGlyph lipgloss.Style
|
|
KeyDesc lipgloss.Style
|
|
Spinner lipgloss.Style
|
|
PanelTitle lipgloss.Style
|
|
Label lipgloss.Style
|
|
Link lipgloss.Style
|
|
}
|
|
|
|
func NewTheme() Theme {
|
|
base := lipgloss.NewStyle().Foreground(lipgloss.Color(colorText))
|
|
return Theme{
|
|
Brand: lipgloss.NewStyle().Foreground(lipgloss.Color(colorViolet)).Bold(true),
|
|
BrandMark: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
|
|
Meta: lipgloss.NewStyle().Foreground(lipgloss.Color(colorCyan)),
|
|
ColHead: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Bold(true),
|
|
Text: base,
|
|
Muted: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)),
|
|
Dim: lipgloss.NewStyle().Foreground(lipgloss.Color(colorDim)),
|
|
Rank: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)),
|
|
RankSel: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
|
|
Headline: base,
|
|
HeadlineSel: lipgloss.NewStyle().Foreground(lipgloss.Color(colorText)).Bold(true),
|
|
SelBar: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
|
|
KeyGlyph: lipgloss.NewStyle().Foreground(lipgloss.Color(colorCyan)).Bold(true),
|
|
KeyDesc: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)),
|
|
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
|
|
PanelTitle: lipgloss.NewStyle().Foreground(lipgloss.Color(colorViolet)).Bold(true),
|
|
Label: lipgloss.NewStyle().Foreground(lipgloss.Color(colorBlue)).Bold(true),
|
|
Link: lipgloss.NewStyle().Foreground(lipgloss.Color(colorCyan)).Underline(true),
|
|
}
|
|
}
|
|
|
|
func (t Theme) fg(color string) lipgloss.Style {
|
|
return lipgloss.NewStyle().Foreground(lipgloss.Color(color))
|
|
}
|
|
|
|
func (t Theme) bandFG(b band) lipgloss.Style {
|
|
return t.fg(bandColor(b))
|
|
}
|
|
|
|
func (t Theme) chip(text, color string) string {
|
|
return lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color(colorVoid)).
|
|
Background(lipgloss.Color(color)).
|
|
Bold(true).
|
|
Render(" " + text + " ")
|
|
}
|
|
|
|
func (t Theme) spectrumBar(frac float64, width int) string {
|
|
if width <= 0 {
|
|
return ""
|
|
}
|
|
filled := int(math.Round(clamp01(frac) * float64(width)))
|
|
stops := []string{colorCyan, colorBlue, colorViolet, colorMagenta}
|
|
var b strings.Builder
|
|
for i := 0; i < width; i++ {
|
|
if i < filled {
|
|
b.WriteString(t.fg(stops[i*len(stops)/width]).Render(barFull))
|
|
} else {
|
|
b.WriteString(t.Dim.Render(barEmpty))
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (t Theme) bandBar(b band, frac float64, width int) string {
|
|
if width <= 0 {
|
|
return ""
|
|
}
|
|
filled := int(math.Round(clamp01(frac) * float64(width)))
|
|
color := bandColor(b)
|
|
var sb strings.Builder
|
|
for i := 0; i < width; i++ {
|
|
if i < filled {
|
|
sb.WriteString(t.fg(color).Render(barFull))
|
|
} else {
|
|
sb.WriteString(t.Dim.Render(barEmpty))
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func (t Theme) rule(width int) string {
|
|
lead := len([]rune(ruleLead)) + 1
|
|
n := width - lead
|
|
if n < 0 {
|
|
return t.fg(colorViolet).Render(ruleLead)
|
|
}
|
|
return t.fg(colorViolet).Render(ruleLead) + " " + t.Dim.Render(strings.Repeat(ruleBody, n))
|
|
}
|