| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- package theme
- import (
- "fmt"
- "image/color"
- "math"
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
- )
- // SystemTheme is a dynamic theme that derives its gray scale colors
- // from the terminal's background color at runtime
- type SystemTheme struct {
- BaseTheme
- terminalBg color.Color
- terminalBgIsDark bool
- }
- // NewSystemTheme creates a new instance of the dynamic system theme
- func NewSystemTheme(terminalBg color.Color, isDark bool) *SystemTheme {
- theme := &SystemTheme{
- terminalBg: terminalBg,
- terminalBgIsDark: isDark,
- }
- theme.initializeColors()
- return theme
- }
- func (t *SystemTheme) Name() string {
- return "system"
- }
- // initializeColors sets up all theme colors
- func (t *SystemTheme) initializeColors() {
- // Generate gray scale based on terminal background
- grays := t.generateGrayScale()
- // Set ANSI colors for primary colors
- t.PrimaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Cyan,
- Light: lipgloss.Cyan,
- }
- t.SecondaryColor = compat.AdaptiveColor{
- Dark: lipgloss.Magenta,
- Light: lipgloss.Magenta,
- }
- t.AccentColor = compat.AdaptiveColor{
- Dark: lipgloss.Cyan,
- Light: lipgloss.Cyan,
- }
- // Status colors using ANSI
- t.ErrorColor = compat.AdaptiveColor{
- Dark: lipgloss.Red,
- Light: lipgloss.Red,
- }
- t.WarningColor = compat.AdaptiveColor{
- Dark: lipgloss.Yellow,
- Light: lipgloss.Yellow,
- }
- t.SuccessColor = compat.AdaptiveColor{
- Dark: lipgloss.Green,
- Light: lipgloss.Green,
- }
- t.InfoColor = compat.AdaptiveColor{
- Dark: lipgloss.Cyan,
- Light: lipgloss.Cyan,
- }
- // Text colors
- t.TextColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- // Derive muted text color from terminal foreground
- t.TextMutedColor = t.generateMutedTextColor()
- // Background colors
- t.BackgroundColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- t.BackgroundPanelColor = grays[2]
- t.BackgroundElementColor = grays[3]
- // Border colors
- t.BorderSubtleColor = grays[6]
- t.BorderColor = grays[7]
- t.BorderActiveColor = grays[8]
- // Diff colors using ANSI colors
- t.DiffAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("2"), // green
- Light: lipgloss.Color("2"),
- }
- t.DiffRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("1"), // red
- Light: lipgloss.Color("1"),
- }
- t.DiffContextColor = grays[7] // Use gray for context
- t.DiffHunkHeaderColor = grays[7]
- t.DiffHighlightAddedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("2"), // green
- Light: lipgloss.Color("2"),
- }
- t.DiffHighlightRemovedColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("1"), // red
- Light: lipgloss.Color("1"),
- }
- // Use subtle gray backgrounds for diff
- t.DiffAddedBgColor = grays[2]
- t.DiffRemovedBgColor = grays[2]
- t.DiffContextBgColor = grays[1]
- t.DiffLineNumberColor = grays[6]
- t.DiffAddedLineNumberBgColor = grays[3]
- t.DiffRemovedLineNumberBgColor = grays[3]
- // Markdown colors using ANSI
- t.MarkdownTextColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- t.MarkdownHeadingColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- t.MarkdownLinkColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("4"), // blue
- Light: lipgloss.Color("4"),
- }
- t.MarkdownLinkTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("6"), // cyan
- Light: lipgloss.Color("6"),
- }
- t.MarkdownCodeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("2"), // green
- Light: lipgloss.Color("2"),
- }
- t.MarkdownBlockQuoteColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("3"), // yellow
- Light: lipgloss.Color("3"),
- }
- t.MarkdownEmphColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("3"), // yellow
- Light: lipgloss.Color("3"),
- }
- t.MarkdownStrongColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- t.MarkdownHorizontalRuleColor = t.BorderColor
- t.MarkdownListItemColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("4"), // blue
- Light: lipgloss.Color("4"),
- }
- t.MarkdownListEnumerationColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("6"), // cyan
- Light: lipgloss.Color("6"),
- }
- t.MarkdownImageColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("4"), // blue
- Light: lipgloss.Color("4"),
- }
- t.MarkdownImageTextColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("6"), // cyan
- Light: lipgloss.Color("6"),
- }
- t.MarkdownCodeBlockColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- // Syntax colors
- t.SyntaxCommentColor = t.TextMutedColor // Use same as muted text
- t.SyntaxKeywordColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("5"), // magenta
- Light: lipgloss.Color("5"),
- }
- t.SyntaxFunctionColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("4"), // blue
- Light: lipgloss.Color("4"),
- }
- t.SyntaxVariableColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- t.SyntaxStringColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("2"), // green
- Light: lipgloss.Color("2"),
- }
- t.SyntaxNumberColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("3"), // yellow
- Light: lipgloss.Color("3"),
- }
- t.SyntaxTypeColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("6"), // cyan
- Light: lipgloss.Color("6"),
- }
- t.SyntaxOperatorColor = compat.AdaptiveColor{
- Dark: lipgloss.Color("6"), // cyan
- Light: lipgloss.Color("6"),
- }
- t.SyntaxPunctuationColor = compat.AdaptiveColor{
- Dark: lipgloss.NoColor{},
- Light: lipgloss.NoColor{},
- }
- }
- // generateGrayScale creates a gray scale based on the terminal background
- func (t *SystemTheme) generateGrayScale() map[int]compat.AdaptiveColor {
- grays := make(map[int]compat.AdaptiveColor)
- r, g, b, _ := t.terminalBg.RGBA()
- bgR := float64(r >> 8)
- bgG := float64(g >> 8)
- bgB := float64(b >> 8)
- luminance := 0.299*bgR + 0.587*bgG + 0.114*bgB
- for i := 1; i <= 12; i++ {
- var stepColor string
- factor := float64(i) / 12.0
- if t.terminalBgIsDark {
- if luminance < 10 {
- grayValue := int(factor * 0.4 * 255)
- stepColor = fmt.Sprintf("#%02x%02x%02x", grayValue, grayValue, grayValue)
- } else {
- newLum := luminance + (255-luminance)*factor*0.4
- ratio := newLum / luminance
- newR := math.Min(bgR*ratio, 255)
- newG := math.Min(bgG*ratio, 255)
- newB := math.Min(bgB*ratio, 255)
- stepColor = fmt.Sprintf("#%02x%02x%02x", int(newR), int(newG), int(newB))
- }
- } else {
- if luminance > 245 {
- grayValue := int(255 - factor*0.4*255)
- stepColor = fmt.Sprintf("#%02x%02x%02x", grayValue, grayValue, grayValue)
- } else {
- newLum := luminance * (1 - factor*0.4)
- ratio := newLum / luminance
- newR := math.Max(bgR*ratio, 0)
- newG := math.Max(bgG*ratio, 0)
- newB := math.Max(bgB*ratio, 0)
- stepColor = fmt.Sprintf("#%02x%02x%02x", int(newR), int(newG), int(newB))
- }
- }
- grays[i] = compat.AdaptiveColor{
- Dark: lipgloss.Color(stepColor),
- Light: lipgloss.Color(stepColor),
- }
- }
- return grays
- }
- // generateMutedTextColor creates a muted gray color based on the terminal background
- func (t *SystemTheme) generateMutedTextColor() compat.AdaptiveColor {
- bgR, bgG, bgB, _ := t.terminalBg.RGBA()
- bgRf := float64(bgR >> 8)
- bgGf := float64(bgG >> 8)
- bgBf := float64(bgB >> 8)
- bgLum := 0.299*bgRf + 0.587*bgGf + 0.114*bgBf
- var grayValue int
- if t.terminalBgIsDark {
- if bgLum < 10 {
- // Very dark/black background
- // grays[3] would be around #2e (46), so we need much lighter
- grayValue = 180 // #b4b4b4
- } else {
- // Scale up for lighter dark backgrounds
- // Ensure we're always significantly brighter than BackgroundElement
- grayValue = min(int(160+(bgLum*0.3)), 200)
- }
- } else {
- if bgLum > 245 {
- // Very light/white background
- // grays[3] would be around #f5 (245), so we need much darker
- grayValue = 75 // #4b4b4b
- } else {
- // Scale down for darker light backgrounds
- // Ensure we're always significantly darker than BackgroundElement
- grayValue = max(int(100-((255-bgLum)*0.2)), 60)
- }
- }
- mutedColor := fmt.Sprintf("#%02x%02x%02x", grayValue, grayValue, grayValue)
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(mutedColor),
- Light: lipgloss.Color(mutedColor),
- }
- }
|