package common import ( "log/slog" "strconv" "strings" // "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "tasksquire/taskwarrior" ) type TableStyle struct { Header lipgloss.Style Cell lipgloss.Style Selected lipgloss.Style } type Palette struct { Primary lipgloss.Style Secondary lipgloss.Style Accent lipgloss.Style Muted lipgloss.Style Border lipgloss.Style Background lipgloss.Style Text lipgloss.Style } type Styles struct { Colors map[string]*lipgloss.Style Palette Palette Base lipgloss.Style Form *huh.Theme TableStyle TableStyle Tab lipgloss.Style ActiveTab lipgloss.Style TabBar lipgloss.Style ColumnFocused lipgloss.Style ColumnBlurred lipgloss.Style ColumnInsert lipgloss.Style } func NewStyles(config *taskwarrior.TWConfig) *Styles { styles := Styles{} colors := make(map[string]*lipgloss.Style) for key, value := range config.GetConfig() { if strings.HasPrefix(key, "color.") { _, color, _ := strings.Cut(key, ".") colors[color] = parseColorString(value) } } styles.Colors = colors // Initialize Palette (Iceberg Light) styles.Palette.Primary = lipgloss.NewStyle().Foreground(lipgloss.Color("#2d539e")) // Blue styles.Palette.Secondary = lipgloss.NewStyle().Foreground(lipgloss.Color("#7759b4")) // Purple styles.Palette.Accent = lipgloss.NewStyle().Foreground(lipgloss.Color("#c57339")) // Orange styles.Palette.Muted = lipgloss.NewStyle().Foreground(lipgloss.Color("#8389a3")) // Grey styles.Palette.Border = lipgloss.NewStyle().Foreground(lipgloss.Color("#cad0de")) // Light Grey Border styles.Palette.Background = lipgloss.NewStyle().Background(lipgloss.Color("#e8e9ec")) // Light Background styles.Palette.Text = lipgloss.NewStyle().Foreground(lipgloss.Color("#33374c")) // Dark Text // Override from config if available (example mapping) if s, ok := styles.Colors["primary"]; ok { styles.Palette.Primary = *s } if s, ok := styles.Colors["secondary"]; ok { styles.Palette.Secondary = *s } if s, ok := styles.Colors["active"]; ok { styles.Palette.Accent = *s } styles.Base = lipgloss.NewStyle().Foreground(styles.Palette.Text.GetForeground()) styles.TableStyle = TableStyle{ // Header: lipgloss.NewStyle().Bold(true).Padding(0, 1).BorderBottom(true), Cell: lipgloss.NewStyle().Padding(0, 1, 0, 0), Header: lipgloss.NewStyle().Bold(true).Padding(0, 1, 0, 0).Underline(true).Foreground(styles.Palette.Primary.GetForeground()), Selected: lipgloss.NewStyle().Bold(true).Reverse(true).Foreground(styles.Palette.Accent.GetForeground()), } formTheme := huh.ThemeBase() formTheme.Focused.Title = formTheme.Focused.Title.Bold(true).Foreground(styles.Palette.Primary.GetForeground()) formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("→ ").Foreground(styles.Palette.Accent.GetForeground()) formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true).Foreground(styles.Palette.Accent.GetForeground()) formTheme.Focused.MultiSelectSelector = formTheme.Focused.MultiSelectSelector.SetString("→ ").Foreground(styles.Palette.Accent.GetForeground()) formTheme.Focused.SelectedPrefix = formTheme.Focused.SelectedPrefix.SetString("✓ ").Foreground(styles.Palette.Accent.GetForeground()) formTheme.Focused.UnselectedPrefix = formTheme.Focused.SelectedPrefix.SetString("• ") formTheme.Blurred.Title = formTheme.Blurred.Title.Bold(true).Foreground(styles.Palette.Muted.GetForeground()) formTheme.Blurred.SelectSelector = formTheme.Blurred.SelectSelector.SetString(" ") formTheme.Blurred.SelectedOption = formTheme.Blurred.SelectedOption.Bold(true) formTheme.Blurred.MultiSelectSelector = formTheme.Blurred.MultiSelectSelector.SetString(" ") formTheme.Blurred.SelectedPrefix = formTheme.Blurred.SelectedPrefix.SetString("✓ ") formTheme.Blurred.UnselectedPrefix = formTheme.Blurred.SelectedPrefix.SetString("• ") styles.Form = formTheme styles.Tab = lipgloss.NewStyle(). Padding(0, 1). Foreground(styles.Palette.Muted.GetForeground()) styles.ActiveTab = styles.Tab. Foreground(styles.Palette.Primary.GetForeground()). Bold(true) styles.TabBar = lipgloss.NewStyle(). Border(lipgloss.NormalBorder(), false, false, true, false). BorderForeground(styles.Palette.Border.GetForeground()). MarginBottom(1) styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1).BorderForeground(styles.Palette.Primary.GetForeground()) styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1).BorderForeground(styles.Palette.Border.GetForeground()) styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1).BorderForeground(styles.Palette.Accent.GetForeground()) return &styles } func (s *Styles) GetModalSize(width, height int) (int, int) { modalWidth := 60 if width < 64 { modalWidth = width - 4 } modalHeight := 20 if height < 24 { modalHeight = height - 4 } return modalWidth, modalHeight } func parseColorString(color string) *lipgloss.Style { if color == "" { return nil } style := lipgloss.NewStyle() if strings.Contains(color, "on") { fgbg := strings.Split(color, "on") fg := strings.TrimSpace(fgbg[0]) bg := strings.TrimSpace(fgbg[1]) if fg != "" { style = style.Foreground(parseColor(fg)) } if bg != "" { style = style.Background(parseColor(bg)) } } else { style = style.Foreground(parseColor(strings.TrimSpace(color))) } return &style } func parseColor(color string) lipgloss.Color { if strings.HasPrefix(color, "rgb") { return lipgloss.Color(convertRgbToAnsi(strings.TrimPrefix(color, "rgb"))) } if strings.HasPrefix(color, "color") { return lipgloss.Color(strings.TrimPrefix(color, "color")) } if strings.HasPrefix(color, "gray") { gray, err := strconv.Atoi(strings.TrimPrefix(color, "gray")) if err != nil { slog.Error("Invalid gray color format") return lipgloss.Color("0") } return lipgloss.Color(strconv.Itoa(gray + 232)) } if ansi, okcolor := colorStrings[color]; okcolor { return lipgloss.Color(strconv.Itoa(ansi)) } slog.Error("Invalid color format") return lipgloss.Color("0") } func convertRgbToAnsi(rgbString string) string { var err error if len(rgbString) != 3 { slog.Error("Invalid RGB color format") return "" } r, err := strconv.Atoi(string(rgbString[0])) if err != nil { slog.Error("Invalid value for R") return "" } g, err := strconv.Atoi(string(rgbString[1])) if err != nil { slog.Error("Invalid value for G") return "" } b, err := strconv.Atoi(string(rgbString[2])) if err != nil { slog.Error("Invalid value for B") return "" } return strconv.Itoa(16 + (36 * r) + (6 * g) + b) } var colorStrings = map[string]int{ "black": 0, "red": 1, "green": 2, "yellow": 3, "blue": 4, "magenta": 5, "cyan": 6, "white": 7, "bright black": 8, "bright red": 9, "bright green": 10, "bright yellow": 11, "bright blue": 12, "bright magenta": 13, "bright cyan": 14, "bright white": 15, }