90 lines
3.1 KiB
Go
90 lines
3.1 KiB
Go
package main
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
|
||
"github.com/charmbracelet/lipgloss"
|
||
"github.com/mattn/go-runewidth"
|
||
)
|
||
|
||
// A restrained, high-contrast palette. Text always includes its meaning, so
|
||
// colors supplement the UI rather than carrying state by themselves.
|
||
type uiTheme struct {
|
||
title, accent, muted, warning, success, active, selected lipgloss.Style
|
||
border lipgloss.Style
|
||
plain bool
|
||
}
|
||
|
||
func newTheme() uiTheme {
|
||
t := uiTheme{plain: os.Getenv("NO_COLOR") != "" || strings.EqualFold(os.Getenv("TERM"), "dumb")}
|
||
if t.plain {
|
||
t.title = lipgloss.NewStyle()
|
||
t.accent = lipgloss.NewStyle()
|
||
t.muted = lipgloss.NewStyle()
|
||
t.warning = lipgloss.NewStyle()
|
||
t.success = lipgloss.NewStyle()
|
||
t.active = lipgloss.NewStyle()
|
||
t.selected = lipgloss.NewStyle()
|
||
t.border = lipgloss.NewStyle()
|
||
return t
|
||
}
|
||
t.title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#D7E9EF"))
|
||
t.accent = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#70D6D1"))
|
||
t.muted = lipgloss.NewStyle().Foreground(lipgloss.Color("#8FA6B2"))
|
||
t.warning = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FFC078"))
|
||
t.success = lipgloss.NewStyle().Foreground(lipgloss.Color("#A8DDA2"))
|
||
t.active = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFC078"))
|
||
t.selected = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#F4FAFC")).Background(lipgloss.Color("#244553"))
|
||
t.border = lipgloss.NewStyle().Foreground(lipgloss.Color("#426573"))
|
||
return t
|
||
}
|
||
|
||
func (t uiTheme) status(status string) string {
|
||
label := "[" + safe(status) + "]"
|
||
switch strings.ToLower(status) {
|
||
case "closed", "done":
|
||
return t.muted.Render(label)
|
||
case "open":
|
||
return t.success.Render(label)
|
||
default:
|
||
return t.active.Render(label)
|
||
}
|
||
}
|
||
|
||
func (t uiTheme) heading(section string, count string, width int) string {
|
||
prefix := "▌ TODOAPP / "
|
||
section = runewidth.Truncate(safe(section), max(1, width-runewidth.StringWidth(prefix)), "…")
|
||
return t.accent.Render("▌ ") + t.title.Render("TODOAPP") + t.muted.Render(" / "+section) + "\n" +
|
||
t.muted.Render(runewidth.Truncate(safe(count), max(1, width), "…"))
|
||
}
|
||
|
||
// box frames content only when the terminal has room for a border. The width
|
||
// argument is the terminal width, not the inner content width.
|
||
func (t uiTheme) box(content string, width, height int) string {
|
||
if width < 50 || height < 14 || t.plain {
|
||
return content
|
||
}
|
||
return t.border.Border(lipgloss.RoundedBorder()).Padding(0, 1).Width(min(width, 100) - 2).Render(content)
|
||
}
|
||
|
||
func (t uiTheme) row(label string, selected bool, width int) string {
|
||
prefix := " "
|
||
if selected {
|
||
prefix = "› "
|
||
}
|
||
text := runewidth.Truncate(prefix+label, max(1, width), "…")
|
||
if selected {
|
||
return t.selected.Width(min(width, 100)).Render(text)
|
||
}
|
||
return text
|
||
}
|
||
|
||
func (t uiTheme) hint(text string, width int) string {
|
||
return t.muted.Render(runewidth.Truncate(text, max(1, width), "…"))
|
||
}
|
||
|
||
func (t uiTheme) alert(text string, width int) string {
|
||
return t.warning.Render(runewidth.Truncate("! "+safe(text), max(1, width), "…"))
|
||
}
|