1package styles
2
3import (
4 "fmt"
5 "image/color"
6
7 "github.com/charmbracelet/bubbles/v2/textarea"
8 tea "github.com/charmbracelet/bubbletea/v2"
9 "github.com/charmbracelet/lipgloss/v2"
10)
11
12type Theme struct {
13 Name string
14 IsDark bool
15
16 Primary color.Color
17 Secondary color.Color
18 Tertiary color.Color
19 Accent color.Color
20
21 BgBase color.Color
22 BgSubtle color.Color
23 BgOverlay color.Color
24
25 FgBase color.Color
26 FgMuted color.Color
27 FgSubtle color.Color
28
29 Border color.Color
30 BorderFocus color.Color
31
32 Success color.Color
33 Error color.Color
34 Warning color.Color
35 Info color.Color
36
37 // TODO: add more syntax colors, maybe just use a chroma theme here.
38 SyntaxBg color.Color
39 SyntaxKeyword color.Color
40 SyntaxString color.Color
41 SyntaxComment color.Color
42
43 styles *Styles
44}
45
46type Styles struct {
47 Base lipgloss.Style
48
49 Title lipgloss.Style
50 Subtitle lipgloss.Style
51 Text lipgloss.Style
52 Muted lipgloss.Style
53 Subtle lipgloss.Style
54
55 Success lipgloss.Style
56 Error lipgloss.Style
57 Warning lipgloss.Style
58 Info lipgloss.Style
59
60 // Inputs
61 TextArea textarea.Styles
62}
63
64func (t *Theme) S() *Styles {
65 if t.styles == nil {
66 t.styles = t.buildStyles()
67 }
68 return t.styles
69}
70
71func (t *Theme) buildStyles() *Styles {
72 base := lipgloss.NewStyle().
73 Background(t.BgBase).
74 Foreground(t.FgBase)
75 return &Styles{
76 Base: base,
77
78 Title: base.
79 Foreground(t.Accent).
80 Bold(true),
81
82 Subtitle: base.
83 Foreground(t.Secondary).
84 Bold(true),
85
86 Text: base,
87
88 Muted: base.Foreground(t.FgMuted),
89
90 Subtle: base.Foreground(t.FgSubtle),
91
92 Success: base.Foreground(t.Success),
93
94 Error: base.Foreground(t.Error),
95
96 Warning: base.Foreground(t.Warning),
97
98 Info: base.Foreground(t.Info),
99
100 TextArea: textarea.Styles{
101 Focused: textarea.StyleState{
102 Base: base,
103 Text: base,
104 LineNumber: base.Foreground(t.FgSubtle),
105 CursorLine: base,
106 CursorLineNumber: base.Foreground(t.FgSubtle),
107 Placeholder: base.Foreground(t.FgMuted),
108 Prompt: base.Foreground(t.Tertiary),
109 },
110 Blurred: textarea.StyleState{
111 Base: base,
112 Text: base.Foreground(t.FgMuted),
113 LineNumber: base.Foreground(t.FgMuted),
114 CursorLine: base,
115 CursorLineNumber: base.Foreground(t.FgMuted),
116 Placeholder: base.Foreground(t.FgMuted),
117 Prompt: base.Foreground(t.FgMuted),
118 },
119 Cursor: textarea.CursorStyle{
120 Color: t.Secondary,
121 Shape: tea.CursorBar,
122 Blink: true,
123 },
124 },
125 }
126}
127
128type Manager struct {
129 themes map[string]*Theme
130 current *Theme
131}
132
133var defaultManager *Manager
134
135func SetDefaultManager(m *Manager) {
136 defaultManager = m
137}
138
139func DefaultManager() *Manager {
140 if defaultManager == nil {
141 defaultManager = NewManager("crush")
142 }
143 return defaultManager
144}
145
146func CurrentTheme() *Theme {
147 if defaultManager == nil {
148 defaultManager = NewManager("crush")
149 }
150 return defaultManager.Current()
151}
152
153func NewManager(defaultTheme string) *Manager {
154 m := &Manager{
155 themes: make(map[string]*Theme),
156 }
157
158 m.Register(NewCrushTheme())
159
160 m.current = m.themes[defaultTheme]
161
162 return m
163}
164
165func (m *Manager) Register(theme *Theme) {
166 m.themes[theme.Name] = theme
167}
168
169func (m *Manager) Current() *Theme {
170 return m.current
171}
172
173func (m *Manager) SetTheme(name string) error {
174 if theme, ok := m.themes[name]; ok {
175 m.current = theme
176 return nil
177 }
178 return fmt.Errorf("theme %s not found", name)
179}
180
181func (m *Manager) List() []string {
182 names := make([]string, 0, len(m.themes))
183 for name := range m.themes {
184 names = append(names, name)
185 }
186 return names
187}
188
189// ParseHex converts hex string to color
190func ParseHex(hex string) color.Color {
191 var r, g, b uint8
192 fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
193 return color.RGBA{R: r, G: g, B: b, A: 255}
194}
195
196// Alpha returns a color with transparency
197func Alpha(c color.Color, alpha uint8) color.Color {
198 r, g, b, _ := c.RGBA()
199 return color.RGBA{
200 R: uint8(r >> 8),
201 G: uint8(g >> 8),
202 B: uint8(b >> 8),
203 A: alpha,
204 }
205}
206
207// Darken makes a color darker by percentage (0-100)
208func Darken(c color.Color, percent float64) color.Color {
209 r, g, b, a := c.RGBA()
210 factor := 1.0 - percent/100.0
211 return color.RGBA{
212 R: uint8(float64(r>>8) * factor),
213 G: uint8(float64(g>>8) * factor),
214 B: uint8(float64(b>>8) * factor),
215 A: uint8(a >> 8),
216 }
217}
218
219// Lighten makes a color lighter by percentage (0-100)
220func Lighten(c color.Color, percent float64) color.Color {
221 r, g, b, a := c.RGBA()
222 factor := percent / 100.0
223 return color.RGBA{
224 R: uint8(min(255, float64(r>>8)+255*factor)),
225 G: uint8(min(255, float64(g>>8)+255*factor)),
226 B: uint8(min(255, float64(b>>8)+255*factor)),
227 A: uint8(a >> 8),
228 }
229}