121 lines
2.9 KiB
Go
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
|
|
}
|
|
}
|