Files

121 lines
2.9 KiB
Go

// Package theme — token warna semantik (PRD §5.4).
//
// Komponen TIDAK boleh memakai kode warna langsung; semua lewat token
// (Base, Surface, Text, Accent, Success, …) yang dipetakan ke palet aktif
// (Mocha/Latte/ANSI terminal), sehingga ganti mode tidak mengubah view.
package theme
import (
"sync"
"charm.land/lipgloss/v2"
)
type Mode string
const (
ModeAuto Mode = "auto" // deteksi background → Mocha (gelap) / Latte (terang)
ModeDark Mode = "dark" // Catppuccin Mocha
ModeLight Mode = "light" // Catppuccin Latte
ModeTerminal Mode = "terminal" // palet ANSI terminal pengguna
)
// Color — "#rrggbb", indeks ANSI ("1".."255"), atau "" = default terminal
// (dipakai mode terminal: background & warna dasar tidak diubah).
type Color string
// Paint mewarnai teks; Color kosong = teks apa adanya (tanpa escape).
func (c Color) Paint(s string) string {
if c == "" {
return s
}
return lipgloss.NewStyle().Foreground(lipgloss.Color(string(c))).Render(s)
}
// Theme — token semantik aktif.
type Theme struct {
Mode Mode
AccentName string
Base, Surface, Overlay Color
Text, Subtext Color
Accent Color
Success, Warning, Danger Color
Info, Hover, Selection Color
}
var (
mu sync.RWMutex
current = build(ModeDark, "mauve", true)
// detectedDark diisi lewat SetDetectedDark saat tea.BackgroundColorMsg
// tiba (mode auto). Default gelap = fallback PRD §5.4.
detectedDark = true
)
// Init dipanggil sekali saat startup (dari config).
func Init(mode Mode, accent string) { Set(mode, accent) }
// Set mengganti tema aktif (validasi nilai; dipakai juga saat runtime).
func Set(mode Mode, accent string) {
m := normalizeMode(mode)
mu.RLock()
dark := detectedDark
mu.RUnlock()
t := build(m, accent, dark)
mu.Lock()
current = t
mu.Unlock()
}
// SetDetectedDark — dipanggil dari Update saat tea.BackgroundColorMsg tiba;
// merakit ulang tema bila mode aktif adalah auto.
// (Query background dilakukan oleh Bubble Tea sendiri — bukan lipgloss langsung,
// karena hanya framework yang boleh membaca stdin di dalam loop input.)
func SetDetectedDark(dark bool) {
mu.Lock()
detectedDark = dark
cur := current
mu.Unlock()
if cur.Mode == ModeAuto {
Set(ModeAuto, cur.AccentName)
}
}
func Current() Theme {
mu.RLock()
defer mu.RUnlock()
return current
}
// CycleMode — urutan tombol cepat `T`: auto → dark → light → terminal.
func CycleMode(m Mode) Mode {
switch m {
case ModeAuto:
return ModeDark
case ModeDark:
return ModeLight
case ModeLight:
return ModeTerminal
default:
return ModeAuto
}
}
// Style — style dasar berwarna token; Color kosong = tanpa warna.
func Style(c Color) lipgloss.Style {
if c == "" {
return lipgloss.NewStyle()
}
return lipgloss.NewStyle().Foreground(lipgloss.Color(string(c)))
}
func normalizeMode(m Mode) Mode {
switch m {
case ModeAuto, ModeDark, ModeLight, ModeTerminal:
return m
default:
return ModeDark
}
}