diff --git a/component/barchart/bar_chart.go b/component/barchart/bar_chart.go index e25abd0..c236b29 100644 --- a/component/barchart/bar_chart.go +++ b/component/barchart/bar_chart.go @@ -68,7 +68,7 @@ func DefaultTheme() Theme { LabelColor: colors.BurndownChartLabelColor, ValueColor: colors.BurndownChartValueColor, BarColor: colors.BurndownChartBarColor, - BackgroundColor: config.GetContentBackgroundColor(), + BackgroundColor: config.GetColors().ContentBackgroundColor, BarGradientFrom: colors.BurndownChartGradientFrom.Start, BarGradientTo: colors.BurndownChartGradientTo.Start, DotChar: '⣿', // braille full cell for dense dot matrix diff --git a/component/barchart/solid.go b/component/barchart/solid.go index 647579f..244d0f5 100644 --- a/component/barchart/solid.go +++ b/component/barchart/solid.go @@ -44,7 +44,7 @@ func barFillColor(bar Bar, row, total int, theme Theme) tcell.Color { // Use adaptive gradient: solid color when gradients disabled if !config.UseGradients { - return config.FallbackBurndownColor + return config.GetColors().FallbackBurndownColor } t := float64(row) / float64(total-1) diff --git a/component/completion_prompt.go b/component/completion_prompt.go index a6b2df8..bf1dfb3 100644 --- a/component/completion_prompt.go +++ b/component/completion_prompt.go @@ -25,10 +25,9 @@ func NewCompletionPrompt(words []string) *CompletionPrompt { inputField := tview.NewInputField() // Configure the input field - inputField.SetFieldBackgroundColor(config.GetContentBackgroundColor()) - inputField.SetFieldTextColor(config.GetContentTextColor()) - colors := config.GetColors() + inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor) + inputField.SetFieldTextColor(colors.ContentTextColor) cp := &CompletionPrompt{ InputField: inputField, words: words, diff --git a/component/date_edit.go b/component/date_edit.go index 8dde5b7..c0afc7c 100644 --- a/component/date_edit.go +++ b/component/date_edit.go @@ -27,8 +27,9 @@ type DateEdit struct { // NewDateEdit creates a new date input field. func NewDateEdit() *DateEdit { inputField := tview.NewInputField() - inputField.SetFieldBackgroundColor(config.GetContentBackgroundColor()) - inputField.SetFieldTextColor(config.GetContentTextColor()) + colors := config.GetColors() + inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor) + inputField.SetFieldTextColor(colors.ContentTextColor) de := &DateEdit{ InputField: inputField, diff --git a/component/edit_select_list.go b/component/edit_select_list.go index 395ff20..87caf3b 100644 --- a/component/edit_select_list.go +++ b/component/edit_select_list.go @@ -29,8 +29,9 @@ func NewEditSelectList(values []string, allowTyping bool) *EditSelectList { inputField := tview.NewInputField() // Configure the input field - inputField.SetFieldBackgroundColor(config.GetContentBackgroundColor()) - inputField.SetFieldTextColor(config.GetContentTextColor()) + colors := config.GetColors() + inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor) + inputField.SetFieldTextColor(colors.ContentTextColor) esl := &EditSelectList{ InputField: inputField, diff --git a/component/int_edit_select.go b/component/int_edit_select.go index ebe4b9f..e785e37 100644 --- a/component/int_edit_select.go +++ b/component/int_edit_select.go @@ -37,8 +37,9 @@ func NewIntEditSelect(min, max int, allowTyping bool) *IntEditSelect { } inputField := tview.NewInputField() - inputField.SetFieldBackgroundColor(config.GetContentBackgroundColor()) - inputField.SetFieldTextColor(config.GetContentTextColor()) + colors := config.GetColors() + inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor) + inputField.SetFieldTextColor(colors.ContentTextColor) ies := &IntEditSelect{ InputField: inputField, diff --git a/component/recurrence_edit.go b/component/recurrence_edit.go index dbb507b..bf681f3 100644 --- a/component/recurrence_edit.go +++ b/component/recurrence_edit.go @@ -31,8 +31,9 @@ type RecurrenceEdit struct { // NewRecurrenceEdit creates a new recurrence editor. func NewRecurrenceEdit() *RecurrenceEdit { inputField := tview.NewInputField() - inputField.SetFieldBackgroundColor(config.GetContentBackgroundColor()) - inputField.SetFieldTextColor(config.GetContentTextColor()) + colors := config.GetColors() + inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor) + inputField.SetFieldTextColor(colors.ContentTextColor) re := &RecurrenceEdit{ InputField: inputField, diff --git a/component/task_list.go b/component/task_list.go index 2e2d7ed..dac3766 100644 --- a/component/task_list.go +++ b/component/task_list.go @@ -37,7 +37,7 @@ func NewTaskList(maxVisibleRows int) *TaskList { Box: tview.NewBox(), maxVisibleRows: maxVisibleRows, idGradient: colors.TaskBoxIDColor, - idFallback: config.FallbackTaskIDColor, + idFallback: colors.FallbackTaskIDColor, titleColor: colors.TaskBoxTitleColor, selectionColor: colors.TaskListSelectionColor, statusDoneColor: colors.TaskListStatusDoneColor, diff --git a/component/task_list_test.go b/component/task_list_test.go index 650fbe2..5a16034 100644 --- a/component/task_list_test.go +++ b/component/task_list_test.go @@ -40,7 +40,7 @@ func TestNewTaskList(t *testing.T) { if tl.idGradient != colors.TaskBoxIDColor { t.Error("Expected ID gradient from config") } - if tl.idFallback != config.FallbackTaskIDColor { + if tl.idFallback != config.GetColors().FallbackTaskIDColor { t.Error("Expected ID fallback from config") } if tl.titleColor != colors.TaskBoxTitleColor { diff --git a/component/word_list.go b/component/word_list.go index e9c18e9..e8507bd 100644 --- a/component/word_list.go +++ b/component/word_list.go @@ -59,7 +59,7 @@ func (w *WordList) Draw(screen tcell.Screen) { } wordStyle := tcell.StyleDefault.Foreground(w.fgColor).Background(w.bgColor) - spaceStyle := tcell.StyleDefault.Background(config.GetContentBackgroundColor()) + spaceStyle := tcell.StyleDefault.Background(config.GetColors().ContentBackgroundColor) currentX := x currentY := y diff --git a/config/colors.go b/config/colors.go index 6eb5c5b..160dfc2 100644 --- a/config/colors.go +++ b/config/colors.go @@ -45,6 +45,11 @@ type ColorConfig struct { TaskDetailEditFocusText string // tview color string like "[white]" TaskDetailTagForeground tcell.Color TaskDetailTagBackground tcell.Color + TaskDetailPlaceholderColor tcell.Color + + // Content area colors (base canvas for editable/readable content) + ContentBackgroundColor tcell.Color + ContentTextColor tcell.Color // Search box colors SearchBoxLabelColor tcell.Color @@ -87,6 +92,13 @@ type ColorConfig struct { HeaderActionViewKeyColor string // tview color string for view action keys HeaderActionViewLabelColor string // tview color string for view action labels + // Plugin-specific colors + DepsEditorBackground tcell.Color // muted slate for dependency editor caption + + // Fallback solid colors for gradient scenarios (used when UseGradients = false) + FallbackTaskIDColor tcell.Color // Deep Sky Blue (end of task ID gradient) + FallbackBurndownColor tcell.Color // Purple (start of burndown gradient) + // Statusline colors (bottom bar, powerline style) StatuslineBg string // hex color for stat segment background, e.g. "#3a3a5c" StatuslineFg string // hex color for stat segment text, e.g. "#cccccc" @@ -142,6 +154,11 @@ func DefaultColors() *ColorConfig { TaskDetailEditFocusText: "[white]", // White text after arrow TaskDetailTagForeground: tcell.NewRGBColor(180, 200, 220), // Light blue-gray text TaskDetailTagBackground: tcell.NewRGBColor(30, 50, 120), // Dark blue background (more bluish) + TaskDetailPlaceholderColor: tcell.ColorGray, // Gray for placeholder text in edit fields + + // Content area (base canvas) + ContentBackgroundColor: tcell.ColorBlack, // dark theme: explicit black + ContentTextColor: tcell.ColorWhite, // dark theme: white text // Search box SearchBoxLabelColor: tcell.ColorWhite, @@ -196,6 +213,13 @@ func DefaultColors() *ColorConfig { HeaderActionViewKeyColor: "#5fafff", // cyan for view-specific actions HeaderActionViewLabelColor: "#808080", // gray for view-specific labels + // Plugin-specific + DepsEditorBackground: tcell.NewHexColor(0x4e5768), // Muted slate + + // Fallback solid colors (no-gradient terminals) + FallbackTaskIDColor: tcell.NewRGBColor(0, 191, 255), // Deep Sky Blue + FallbackBurndownColor: tcell.NewRGBColor(134, 90, 214), // Purple + // Statusline (Nord theme) StatuslineBg: "#434c5e", // Nord polar night 3 StatuslineFg: "#d8dee9", // Nord snow storm 1 @@ -221,30 +245,14 @@ var UseGradients bool // Screen-wide gradients show more banding on 256-color terminals, so require truecolor var UseWideGradients bool -// Plugin-specific background colors for code-only plugins -var ( - // DepsEditorBackground: muted slate for the dependency editor caption - DepsEditorBackground = tcell.NewHexColor(0x4e5768) -) - -// Fallback solid colors for gradient scenarios (used when UseGradients = false) -var ( - // Caption title fallback: Royal Blue (end of gradient) - FallbackTitleColor = tcell.NewRGBColor(65, 105, 225) - // Task ID fallback: Deep Sky Blue (end of gradient) - FallbackTaskIDColor = tcell.NewRGBColor(0, 191, 255) - // Burndown chart fallback: Purple (start of gradient) - FallbackBurndownColor = tcell.NewRGBColor(134, 90, 214) - // Caption row fallback: Midpoint of Midnight Blue to Royal Blue - FallbackCaptionColor = tcell.NewRGBColor(45, 65, 169) -) - // GetColors returns the global color configuration with theme-aware overrides func GetColors() *ColorConfig { if !colorsInitialized { globalColors = DefaultColors() // Apply theme-aware overrides for critical text colors if GetEffectiveTheme() == "light" { + globalColors.ContentBackgroundColor = tcell.ColorDefault + globalColors.ContentTextColor = tcell.ColorBlack globalColors.SearchBoxLabelColor = tcell.ColorBlack globalColors.SearchBoxTextColor = tcell.ColorBlack globalColors.InputFieldTextColor = tcell.ColorBlack diff --git a/config/loader.go b/config/loader.go index cad66cf..97682c8 100644 --- a/config/loader.go +++ b/config/loader.go @@ -10,7 +10,6 @@ import ( "path/filepath" "strings" - "github.com/gdamore/tcell/v2" "github.com/spf13/pflag" "github.com/spf13/viper" "gopkg.in/yaml.v3" @@ -375,24 +374,6 @@ func GetEffectiveTheme() string { return "dark" // default fallback } -// GetContentBackgroundColor returns the background color for markdown content areas -// Dark theme needs black background for light text; light theme uses terminal default -func GetContentBackgroundColor() tcell.Color { - if GetEffectiveTheme() == "dark" { - return tcell.ColorBlack - } - return tcell.ColorDefault -} - -// GetContentTextColor returns the appropriate text color for content areas -// Dark theme uses white text; light theme uses black text -func GetContentTextColor() tcell.Color { - if GetEffectiveTheme() == "dark" { - return tcell.ColorWhite - } - return tcell.ColorBlack -} - // GetGradientThreshold returns the minimum color count required for gradients // Valid values: 16, 256, 16777216 (truecolor) func GetGradientThreshold() int { diff --git a/controller/input_router.go b/controller/input_router.go index 40742ca..bfdabb4 100644 --- a/controller/input_router.go +++ b/controller/input_router.go @@ -232,7 +232,7 @@ func (ir *InputRouter) openDepsEditor(taskID string) bool { Description: model.DepsEditorViewDesc, ConfigIndex: -1, Type: "tiki", - Background: config.DepsEditorBackground, + Background: config.GetColors().DepsEditorBackground, }, TaskID: taskID, Lanes: []plugin.TikiLane{ diff --git a/view/borders.go b/view/borders.go index ae03567..2436add 100644 --- a/view/borders.go +++ b/view/borders.go @@ -25,7 +25,7 @@ func DrawSingleLineBorder(screen tcell.Screen, x, y, width, height int) { } colors := config.GetColors() - style := tcell.StyleDefault.Foreground(colors.TaskBoxUnselectedBorder).Background(config.GetContentBackgroundColor()) + style := tcell.StyleDefault.Foreground(colors.TaskBoxUnselectedBorder).Background(colors.ContentBackgroundColor) DrawSingleLineBorderWithStyle(screen, x, y, width, height, style) } diff --git a/view/markdown/navigable_markdown.go b/view/markdown/navigable_markdown.go index 336222f..a9e68ff 100644 --- a/view/markdown/navigable_markdown.go +++ b/view/markdown/navigable_markdown.go @@ -47,7 +47,7 @@ func NewNavigableMarkdown(cfg NavigableMarkdownConfig) *NavigableMarkdown { renderer = renderer.WithCodeBorder(b) } nm.viewer.SetRenderer(renderer) - nm.viewer.SetBackgroundColor(config.GetContentBackgroundColor()) + nm.viewer.SetBackgroundColor(config.GetColors().ContentBackgroundColor) if cfg.ImageManager != nil && cfg.ImageManager.Supported() { nm.viewer.SetImageManager(cfg.ImageManager) } diff --git a/view/search_box.go b/view/search_box.go index 372e134..29acda1 100644 --- a/view/search_box.go +++ b/view/search_box.go @@ -22,8 +22,8 @@ func NewSearchBox() *SearchBox { // Configure the input field (border drawn manually in Draw) inputField.SetLabel("> ") inputField.SetLabelColor(colors.SearchBoxLabelColor) - inputField.SetFieldBackgroundColor(config.GetContentBackgroundColor()) - inputField.SetFieldTextColor(config.GetContentTextColor()) + inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor) + inputField.SetFieldTextColor(colors.ContentTextColor) inputField.SetBorder(false) sb := &SearchBox{ @@ -60,7 +60,7 @@ func (sb *SearchBox) Draw(screen tcell.Screen) { } // Fill interior with theme-aware background color - bgColor := config.GetContentBackgroundColor() + bgColor := config.GetColors().ContentBackgroundColor bgStyle := tcell.StyleDefault.Background(bgColor) for row := y; row < y+height; row++ { for col := x; col < x+width; col++ { diff --git a/view/task_box.go b/view/task_box.go index 047ef8f..27333a6 100644 --- a/view/task_box.go +++ b/view/task_box.go @@ -30,7 +30,7 @@ func applyFrameStyle(frame *tview.Frame, selected bool, colors *config.ColorConf // buildCompactTaskContent builds the content string for compact task display func buildCompactTaskContent(task *taskpkg.Task, colors *config.ColorConfig, availableWidth int) string { emoji := taskpkg.TypeEmoji(task.Type) - idGradient := gradient.RenderAdaptiveGradientText(task.ID, colors.TaskBoxIDColor, config.FallbackTaskIDColor) + idGradient := gradient.RenderAdaptiveGradientText(task.ID, colors.TaskBoxIDColor, colors.FallbackTaskIDColor) truncatedTitle := tview.Escape(util.TruncateText(task.Title, availableWidth)) priorityEmoji := taskpkg.PriorityLabel(task.Priority) pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor) @@ -45,7 +45,7 @@ func buildCompactTaskContent(task *taskpkg.Task, colors *config.ColorConfig, ava // buildExpandedTaskContent builds the content string for expanded task display func buildExpandedTaskContent(task *taskpkg.Task, colors *config.ColorConfig, availableWidth int) string { emoji := taskpkg.TypeEmoji(task.Type) - idGradient := gradient.RenderAdaptiveGradientText(task.ID, colors.TaskBoxIDColor, config.FallbackTaskIDColor) + idGradient := gradient.RenderAdaptiveGradientText(task.ID, colors.TaskBoxIDColor, colors.FallbackTaskIDColor) truncatedTitle := tview.Escape(util.TruncateText(task.Title, availableWidth)) // Extract first 3 lines of description diff --git a/view/taskdetail/base.go b/view/taskdetail/base.go index 40223a7..982d757 100644 --- a/view/taskdetail/base.go +++ b/view/taskdetail/base.go @@ -114,7 +114,7 @@ func (b *Base) assembleMetadataBox( metadataBox := tview.NewFrame(metadataContainer).SetBorders(0, 0, 0, 0, 0, 0) metadataBox.SetBorder(true).SetTitle( - fmt.Sprintf(" %s ", gradient.RenderAdaptiveGradientText(task.ID, colors.TaskDetailIDColor, config.FallbackTaskIDColor)), + fmt.Sprintf(" %s ", gradient.RenderAdaptiveGradientText(task.ID, colors.TaskDetailIDColor, colors.FallbackTaskIDColor)), ).SetBorderColor(colors.TaskBoxUnselectedBorder) metadataBox.SetBorderPadding(1, 0, 2, 2) diff --git a/view/taskdetail/task_edit_view.go b/view/taskdetail/task_edit_view.go index a751b8a..34931ff 100644 --- a/view/taskdetail/task_edit_view.go +++ b/view/taskdetail/task_edit_view.go @@ -381,7 +381,7 @@ func (ev *TaskEditView) ensureTagsTextArea(task *taskpkg.Task) *tview.TextArea { ev.tagsTextArea.SetBorder(false) ev.tagsTextArea.SetBorderPadding(1, 1, 2, 2) ev.tagsTextArea.SetPlaceholder("Enter tags separated by spaces") - ev.tagsTextArea.SetPlaceholderStyle(tcell.StyleDefault.Foreground(tcell.ColorGray)) + ev.tagsTextArea.SetPlaceholderStyle(tcell.StyleDefault.Foreground(config.GetColors().TaskDetailPlaceholderColor)) ev.tagsTextArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyCtrlS { @@ -448,7 +448,7 @@ func (ev *TaskEditView) ensureTitleInput(task *taskpkg.Task) *tview.InputField { if ev.titleInput == nil { colors := config.GetColors() ev.titleInput = tview.NewInputField() - ev.titleInput.SetFieldBackgroundColor(config.GetContentBackgroundColor()) + ev.titleInput.SetFieldBackgroundColor(colors.ContentBackgroundColor) ev.titleInput.SetFieldTextColor(colors.InputFieldTextColor) ev.titleInput.SetBorder(false)