mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
group colors by value
This commit is contained in:
parent
b5f2ad66fa
commit
0be985a077
39 changed files with 766 additions and 365 deletions
|
|
@ -64,11 +64,11 @@ type BarChart struct {
|
||||||
func DefaultTheme() Theme {
|
func DefaultTheme() Theme {
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
return Theme{
|
return Theme{
|
||||||
AxisColor: colors.BurndownChartAxisColor,
|
AxisColor: colors.BurndownChartAxisColor.TCell(),
|
||||||
LabelColor: colors.BurndownChartLabelColor,
|
LabelColor: colors.BurndownChartLabelColor.TCell(),
|
||||||
ValueColor: colors.BurndownChartValueColor,
|
ValueColor: colors.BurndownChartValueColor.TCell(),
|
||||||
BarColor: colors.BurndownChartBarColor,
|
BarColor: colors.BurndownChartBarColor.TCell(),
|
||||||
BackgroundColor: config.GetColors().ContentBackgroundColor,
|
BackgroundColor: config.GetColors().ContentBackgroundColor.TCell(),
|
||||||
BarGradientFrom: colors.BurndownChartGradientFrom.Start,
|
BarGradientFrom: colors.BurndownChartGradientFrom.Start,
|
||||||
BarGradientTo: colors.BurndownChartGradientTo.Start,
|
BarGradientTo: colors.BurndownChartGradientTo.Start,
|
||||||
DotChar: '⣿', // braille full cell for dense dot matrix
|
DotChar: '⣿', // braille full cell for dense dot matrix
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func barFillColor(bar Bar, row, total int, theme Theme) tcell.Color {
|
||||||
|
|
||||||
// Use adaptive gradient: solid color when gradients disabled
|
// Use adaptive gradient: solid color when gradients disabled
|
||||||
if !config.UseGradients {
|
if !config.UseGradients {
|
||||||
return config.GetColors().FallbackBurndownColor
|
return config.GetColors().FallbackBurndownColor.TCell()
|
||||||
}
|
}
|
||||||
|
|
||||||
t := float64(row) / float64(total-1)
|
t := float64(row) / float64(total-1)
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@ func NewCompletionPrompt(words []string) *CompletionPrompt {
|
||||||
|
|
||||||
// Configure the input field
|
// Configure the input field
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
inputField.SetFieldTextColor(colors.ContentTextColor)
|
inputField.SetFieldTextColor(colors.ContentTextColor.TCell())
|
||||||
cp := &CompletionPrompt{
|
cp := &CompletionPrompt{
|
||||||
InputField: inputField,
|
InputField: inputField,
|
||||||
words: words,
|
words: words,
|
||||||
hintColor: colors.CompletionHintColor,
|
hintColor: colors.CompletionHintColor.TCell(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return cp
|
return cp
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ type DateEdit struct {
|
||||||
func NewDateEdit() *DateEdit {
|
func NewDateEdit() *DateEdit {
|
||||||
inputField := tview.NewInputField()
|
inputField := tview.NewInputField()
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
inputField.SetFieldTextColor(colors.ContentTextColor)
|
inputField.SetFieldTextColor(colors.ContentTextColor.TCell())
|
||||||
|
|
||||||
de := &DateEdit{
|
de := &DateEdit{
|
||||||
InputField: inputField,
|
InputField: inputField,
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ func NewEditSelectList(values []string, allowTyping bool) *EditSelectList {
|
||||||
|
|
||||||
// Configure the input field
|
// Configure the input field
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
inputField.SetFieldTextColor(colors.ContentTextColor)
|
inputField.SetFieldTextColor(colors.ContentTextColor.TCell())
|
||||||
|
|
||||||
esl := &EditSelectList{
|
esl := &EditSelectList{
|
||||||
InputField: inputField,
|
InputField: inputField,
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ func NewIntEditSelect(min, max int, allowTyping bool) *IntEditSelect {
|
||||||
|
|
||||||
inputField := tview.NewInputField()
|
inputField := tview.NewInputField()
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
inputField.SetFieldTextColor(colors.ContentTextColor)
|
inputField.SetFieldTextColor(colors.ContentTextColor.TCell())
|
||||||
|
|
||||||
ies := &IntEditSelect{
|
ies := &IntEditSelect{
|
||||||
InputField: inputField,
|
InputField: inputField,
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ type RecurrenceEdit struct {
|
||||||
func NewRecurrenceEdit() *RecurrenceEdit {
|
func NewRecurrenceEdit() *RecurrenceEdit {
|
||||||
inputField := tview.NewInputField()
|
inputField := tview.NewInputField()
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
inputField.SetFieldTextColor(colors.ContentTextColor)
|
inputField.SetFieldTextColor(colors.ContentTextColor.TCell())
|
||||||
|
|
||||||
re := &RecurrenceEdit{
|
re := &RecurrenceEdit{
|
||||||
InputField: inputField,
|
InputField: inputField,
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,12 @@ type TaskList struct {
|
||||||
selectionIndex int
|
selectionIndex int
|
||||||
idColumnWidth int // computed from widest ID
|
idColumnWidth int // computed from widest ID
|
||||||
idGradient config.Gradient // gradient for ID text
|
idGradient config.Gradient // gradient for ID text
|
||||||
idFallback tcell.Color // fallback solid color for ID
|
idFallback config.Color // fallback solid color for ID
|
||||||
titleColor string // tview color tag for title, e.g. "[#b8b8b8]"
|
titleColor config.Color // color for title text
|
||||||
selectionColor string // tview color tag for selected row highlight
|
selectionColor config.Color // foreground color for selected row highlight
|
||||||
statusDoneColor string // tview color tag for done status indicator
|
selectionBgColor config.Color // background color for selected row highlight
|
||||||
statusPendingColor string // tview color tag for pending status indicator
|
statusDoneColor config.Color // color for done status indicator
|
||||||
|
statusPendingColor config.Color // color for pending status indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTaskList creates a new TaskList with the given maximum visible row count.
|
// NewTaskList creates a new TaskList with the given maximum visible row count.
|
||||||
|
|
@ -39,7 +40,8 @@ func NewTaskList(maxVisibleRows int) *TaskList {
|
||||||
idGradient: colors.TaskBoxIDColor,
|
idGradient: colors.TaskBoxIDColor,
|
||||||
idFallback: colors.FallbackTaskIDColor,
|
idFallback: colors.FallbackTaskIDColor,
|
||||||
titleColor: colors.TaskBoxTitleColor,
|
titleColor: colors.TaskBoxTitleColor,
|
||||||
selectionColor: colors.TaskListSelectionColor,
|
selectionColor: colors.TaskListSelectionFg,
|
||||||
|
selectionBgColor: colors.TaskListSelectionBg,
|
||||||
statusDoneColor: colors.TaskListStatusDoneColor,
|
statusDoneColor: colors.TaskListStatusDoneColor,
|
||||||
statusPendingColor: colors.TaskListStatusPendingColor,
|
statusPendingColor: colors.TaskListStatusPendingColor,
|
||||||
}
|
}
|
||||||
|
|
@ -92,14 +94,14 @@ func (tl *TaskList) ScrollDown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIDColors overrides the gradient and fallback color for the ID column.
|
// SetIDColors overrides the gradient and fallback color for the ID column.
|
||||||
func (tl *TaskList) SetIDColors(g config.Gradient, fallback tcell.Color) *TaskList {
|
func (tl *TaskList) SetIDColors(g config.Gradient, fallback config.Color) *TaskList {
|
||||||
tl.idGradient = g
|
tl.idGradient = g
|
||||||
tl.idFallback = fallback
|
tl.idFallback = fallback
|
||||||
return tl
|
return tl
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTitleColor overrides the tview color tag for the title column.
|
// SetTitleColor overrides the color for the title column.
|
||||||
func (tl *TaskList) SetTitleColor(color string) *TaskList {
|
func (tl *TaskList) SetTitleColor(color config.Color) *TaskList {
|
||||||
tl.titleColor = color
|
tl.titleColor = color
|
||||||
return tl
|
return tl
|
||||||
}
|
}
|
||||||
|
|
@ -134,9 +136,9 @@ func (tl *TaskList) buildRow(t *task.Task, selected bool, width int) string {
|
||||||
// Status indicator: done = checkmark, else circle
|
// Status indicator: done = checkmark, else circle
|
||||||
var statusIndicator string
|
var statusIndicator string
|
||||||
if config.GetStatusRegistry().IsDone(string(t.Status)) {
|
if config.GetStatusRegistry().IsDone(string(t.Status)) {
|
||||||
statusIndicator = tl.statusDoneColor + "\u2713[-]"
|
statusIndicator = tl.statusDoneColor.Tag().String() + "\u2713[-]"
|
||||||
} else {
|
} else {
|
||||||
statusIndicator = tl.statusPendingColor + "\u25CB[-]"
|
statusIndicator = tl.statusPendingColor.Tag().String() + "\u25CB[-]"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradient-rendered ID, padded to idColumnWidth
|
// Gradient-rendered ID, padded to idColumnWidth
|
||||||
|
|
@ -151,10 +153,10 @@ func (tl *TaskList) buildRow(t *task.Task, selected bool, width int) string {
|
||||||
titleAvailable := max(width-1-1-tl.idColumnWidth-1, 0)
|
titleAvailable := max(width-1-1-tl.idColumnWidth-1, 0)
|
||||||
truncatedTitle := tview.Escape(util.TruncateText(t.Title, titleAvailable))
|
truncatedTitle := tview.Escape(util.TruncateText(t.Title, titleAvailable))
|
||||||
|
|
||||||
row := fmt.Sprintf("%s %s %s%s[-]", statusIndicator, idText, tl.titleColor, truncatedTitle)
|
row := fmt.Sprintf("%s %s %s%s[-]", statusIndicator, idText, tl.titleColor.Tag().String(), truncatedTitle)
|
||||||
|
|
||||||
if selected {
|
if selected {
|
||||||
row = tl.selectionColor + row
|
row = tl.selectionColor.Tag().WithBg(tl.selectionBgColor).String() + row
|
||||||
}
|
}
|
||||||
|
|
||||||
return row
|
return row
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ func TestFewerItemsThanViewport(t *testing.T) {
|
||||||
func TestSetIDColors(t *testing.T) {
|
func TestSetIDColors(t *testing.T) {
|
||||||
tl := NewTaskList(10)
|
tl := NewTaskList(10)
|
||||||
g := config.Gradient{Start: [3]int{255, 0, 0}, End: [3]int{0, 255, 0}}
|
g := config.Gradient{Start: [3]int{255, 0, 0}, End: [3]int{0, 255, 0}}
|
||||||
fb := tcell.ColorRed
|
fb := config.NewColor(tcell.ColorRed)
|
||||||
|
|
||||||
result := tl.SetIDColors(g, fb)
|
result := tl.SetIDColors(g, fb)
|
||||||
if result != tl {
|
if result != tl {
|
||||||
|
|
@ -197,12 +197,13 @@ func TestSetIDColors(t *testing.T) {
|
||||||
|
|
||||||
func TestSetTitleColor(t *testing.T) {
|
func TestSetTitleColor(t *testing.T) {
|
||||||
tl := NewTaskList(10)
|
tl := NewTaskList(10)
|
||||||
result := tl.SetTitleColor("[#ff0000]")
|
c := config.NewColor(tcell.ColorRed)
|
||||||
|
result := tl.SetTitleColor(c)
|
||||||
if result != tl {
|
if result != tl {
|
||||||
t.Error("SetTitleColor should return self for chaining")
|
t.Error("SetTitleColor should return self for chaining")
|
||||||
}
|
}
|
||||||
if tl.titleColor != "[#ff0000]" {
|
if tl.titleColor != c {
|
||||||
t.Errorf("Expected [#ff0000], got %s", tl.titleColor)
|
t.Errorf("Expected color red, got %v", tl.titleColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,14 +271,16 @@ func TestBuildRow(t *testing.T) {
|
||||||
|
|
||||||
t.Run("selected row has selection color prefix", func(t *testing.T) {
|
t.Run("selected row has selection color prefix", func(t *testing.T) {
|
||||||
row := tl.buildRow(pendingTask, true, width)
|
row := tl.buildRow(pendingTask, true, width)
|
||||||
if !strings.HasPrefix(row, tl.selectionColor) {
|
selTag := tl.selectionColor.Tag().WithBg(tl.selectionBgColor).String()
|
||||||
t.Errorf("selected row should start with selection color %q", tl.selectionColor)
|
if !strings.HasPrefix(row, selTag) {
|
||||||
|
t.Errorf("selected row should start with selection color %q", selTag)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("unselected row has no selection prefix", func(t *testing.T) {
|
t.Run("unselected row has no selection prefix", func(t *testing.T) {
|
||||||
row := tl.buildRow(pendingTask, false, width)
|
row := tl.buildRow(pendingTask, false, width)
|
||||||
if strings.HasPrefix(row, tl.selectionColor) {
|
selTag := tl.selectionColor.Tag().WithBg(tl.selectionBgColor).String()
|
||||||
|
if strings.HasPrefix(row, selTag) {
|
||||||
t.Error("unselected row should not start with selection color")
|
t.Error("unselected row should not start with selection color")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import (
|
||||||
type WordList struct {
|
type WordList struct {
|
||||||
*tview.Box
|
*tview.Box
|
||||||
words []string
|
words []string
|
||||||
fgColor tcell.Color
|
fgColor config.Color
|
||||||
bgColor tcell.Color
|
bgColor config.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWordList creates a new WordList component.
|
// NewWordList creates a new WordList component.
|
||||||
|
|
@ -43,7 +43,7 @@ func (w *WordList) GetWords() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetColors sets the foreground and background colors.
|
// SetColors sets the foreground and background colors.
|
||||||
func (w *WordList) SetColors(fg, bg tcell.Color) *WordList {
|
func (w *WordList) SetColors(fg, bg config.Color) *WordList {
|
||||||
w.fgColor = fg
|
w.fgColor = fg
|
||||||
w.bgColor = bg
|
w.bgColor = bg
|
||||||
return w
|
return w
|
||||||
|
|
@ -58,8 +58,8 @@ func (w *WordList) Draw(screen tcell.Screen) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wordStyle := tcell.StyleDefault.Foreground(w.fgColor).Background(w.bgColor)
|
wordStyle := tcell.StyleDefault.Foreground(w.fgColor.TCell()).Background(w.bgColor.TCell())
|
||||||
spaceStyle := tcell.StyleDefault.Background(config.GetColors().ContentBackgroundColor)
|
spaceStyle := tcell.StyleDefault.Background(config.GetColors().ContentBackgroundColor.TCell())
|
||||||
|
|
||||||
currentX := x
|
currentX := x
|
||||||
currentY := y
|
currentY := y
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,8 @@ func TestGetWords(t *testing.T) {
|
||||||
|
|
||||||
func TestSetColors(t *testing.T) {
|
func TestSetColors(t *testing.T) {
|
||||||
wl := NewWordList([]string{"test"})
|
wl := NewWordList([]string{"test"})
|
||||||
fg := tcell.ColorRed
|
fg := config.NewColor(tcell.ColorRed)
|
||||||
bg := tcell.ColorGreen
|
bg := config.NewColor(tcell.ColorGreen)
|
||||||
|
|
||||||
result := wl.SetColors(fg, bg)
|
result := wl.SetColors(fg, bg)
|
||||||
|
|
||||||
|
|
|
||||||
113
config/color.go
Normal file
113
config/color.go
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
// Unified color type that stores a single color and produces tcell, hex, and tview tag forms.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Color is a unified color representation backed by tcell.Color.
|
||||||
|
// Zero value wraps tcell.ColorDefault (transparent/inherit).
|
||||||
|
type Color struct {
|
||||||
|
color tcell.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColor creates a Color from a tcell.Color value.
|
||||||
|
func NewColor(c tcell.Color) Color {
|
||||||
|
return Color{color: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorHex creates a Color from a hex string like "#rrggbb" or "rrggbb".
|
||||||
|
func NewColorHex(hex string) Color {
|
||||||
|
return Color{color: tcell.GetColor(hex)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorRGB creates a Color from individual R, G, B components (0-255).
|
||||||
|
func NewColorRGB(r, g, b int32) Color {
|
||||||
|
return Color{color: tcell.NewRGBColor(r, g, b)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultColor returns a Color wrapping tcell.ColorDefault (transparent/inherit).
|
||||||
|
func DefaultColor() Color {
|
||||||
|
return Color{color: tcell.ColorDefault}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCell returns the underlying tcell.Color for use with tview widget APIs.
|
||||||
|
func (c Color) TCell() tcell.Color {
|
||||||
|
return c.color
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB returns the red, green, blue components of the color.
|
||||||
|
func (c Color) RGB() (int32, int32, int32) {
|
||||||
|
return c.color.RGB()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex returns the color as a "#rrggbb" hex string.
|
||||||
|
// Returns "-" for ColorDefault (tview's convention for default/transparent).
|
||||||
|
func (c Color) Hex() string {
|
||||||
|
if c.color == tcell.ColorDefault {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
r, g, b := c.color.RGB()
|
||||||
|
return fmt.Sprintf("#%02x%02x%02x", r, g, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag returns a ColorTag builder for constructing tview color tags.
|
||||||
|
func (c Color) Tag() ColorTag {
|
||||||
|
return ColorTag{fg: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefault returns true if this is the default/transparent color.
|
||||||
|
func (c Color) IsDefault() bool {
|
||||||
|
return c.color == tcell.ColorDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorTag is a composable builder for tview [fg:bg:attr] color tags.
|
||||||
|
// Use Color.Tag() to create one, then chain Bold() / WithBg() as needed.
|
||||||
|
type ColorTag struct {
|
||||||
|
fg Color
|
||||||
|
bg *Color
|
||||||
|
bold bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bold returns a new ColorTag with the bold attribute set.
|
||||||
|
func (t ColorTag) Bold() ColorTag {
|
||||||
|
t.bold = true
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBg returns a new ColorTag with the given background color.
|
||||||
|
func (t ColorTag) WithBg(c Color) ColorTag {
|
||||||
|
t.bg = &c
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// String renders the tview color tag string.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// Color.Tag().String() → "[#rrggbb]"
|
||||||
|
// Color.Tag().Bold().String() → "[#rrggbb::b]"
|
||||||
|
// Color.Tag().WithBg(bg).String() → "[#rrggbb:#rrggbb]"
|
||||||
|
func (t ColorTag) String() string {
|
||||||
|
fg := t.fg.Hex()
|
||||||
|
|
||||||
|
hasBg := t.bg != nil
|
||||||
|
if !hasBg && !t.bold {
|
||||||
|
return "[" + fg + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
bg := "-"
|
||||||
|
if hasBg {
|
||||||
|
bg = t.bg.Hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
attr := ""
|
||||||
|
if t.bold {
|
||||||
|
attr = "b"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "[" + fg + ":" + bg + ":" + attr + "]"
|
||||||
|
}
|
||||||
146
config/color_test.go
Normal file
146
config/color_test.go
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewColor(t *testing.T) {
|
||||||
|
c := NewColor(tcell.ColorYellow)
|
||||||
|
if c.TCell() != tcell.ColorYellow {
|
||||||
|
t.Errorf("TCell() = %v, want %v", c.TCell(), tcell.ColorYellow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewColorHex(t *testing.T) {
|
||||||
|
c := NewColorHex("#ff8000")
|
||||||
|
r, g, b := c.RGB()
|
||||||
|
if r != 255 || g != 128 || b != 0 {
|
||||||
|
t.Errorf("RGB() = (%d, %d, %d), want (255, 128, 0)", r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewColorRGB(t *testing.T) {
|
||||||
|
c := NewColorRGB(10, 20, 30)
|
||||||
|
r, g, b := c.RGB()
|
||||||
|
if r != 10 || g != 20 || b != 30 {
|
||||||
|
t.Errorf("RGB() = (%d, %d, %d), want (10, 20, 30)", r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultColor(t *testing.T) {
|
||||||
|
c := DefaultColor()
|
||||||
|
if !c.IsDefault() {
|
||||||
|
t.Error("DefaultColor().IsDefault() = false, want true")
|
||||||
|
}
|
||||||
|
if c.TCell() != tcell.ColorDefault {
|
||||||
|
t.Errorf("TCell() = %v, want ColorDefault", c.TCell())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColor_Hex(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
c Color
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"black", NewColorRGB(0, 0, 0), "#000000"},
|
||||||
|
{"white", NewColorRGB(255, 255, 255), "#ffffff"},
|
||||||
|
{"red", NewColorRGB(255, 0, 0), "#ff0000"},
|
||||||
|
{"default", DefaultColor(), "-"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.c.Hex(); got != tt.want {
|
||||||
|
t.Errorf("Hex() = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColor_IsDefault(t *testing.T) {
|
||||||
|
if NewColor(tcell.ColorWhite).IsDefault() {
|
||||||
|
t.Error("white.IsDefault() = true, want false")
|
||||||
|
}
|
||||||
|
if !NewColor(tcell.ColorDefault).IsDefault() {
|
||||||
|
t.Error("default.IsDefault() = false, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_String(t *testing.T) {
|
||||||
|
c := NewColorRGB(255, 128, 0)
|
||||||
|
got := c.Tag().String()
|
||||||
|
want := "[#ff8000]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Tag().String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_Bold(t *testing.T) {
|
||||||
|
c := NewColorRGB(255, 128, 0)
|
||||||
|
got := c.Tag().Bold().String()
|
||||||
|
want := "[#ff8000:-:b]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Tag().Bold().String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_WithBg(t *testing.T) {
|
||||||
|
fg := NewColorRGB(255, 255, 255)
|
||||||
|
bg := NewColorHex("#3a5f8a")
|
||||||
|
got := fg.Tag().WithBg(bg).String()
|
||||||
|
want := "[#ffffff:#3a5f8a:]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Tag().WithBg().String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_BoldWithBg(t *testing.T) {
|
||||||
|
fg := NewColorRGB(255, 128, 0)
|
||||||
|
bg := NewColorRGB(0, 0, 0)
|
||||||
|
got := fg.Tag().Bold().WithBg(bg).String()
|
||||||
|
want := "[#ff8000:#000000:b]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Tag().Bold().WithBg().String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_WithBgBold(t *testing.T) {
|
||||||
|
// order shouldn't matter
|
||||||
|
fg := NewColorRGB(255, 128, 0)
|
||||||
|
bg := NewColorRGB(0, 0, 0)
|
||||||
|
got := fg.Tag().WithBg(bg).Bold().String()
|
||||||
|
want := "[#ff8000:#000000:b]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Tag().WithBg().Bold().String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_DefaultFg(t *testing.T) {
|
||||||
|
c := DefaultColor()
|
||||||
|
got := c.Tag().String()
|
||||||
|
want := "[-]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("DefaultColor().Tag().String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorTag_DefaultBg(t *testing.T) {
|
||||||
|
fg := NewColorRGB(255, 0, 0)
|
||||||
|
bg := DefaultColor()
|
||||||
|
got := fg.Tag().WithBg(bg).String()
|
||||||
|
want := "[#ff0000:-:]"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Tag().WithBg(default).String() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorHexRoundTrip(t *testing.T) {
|
||||||
|
original := "#5e81ac"
|
||||||
|
c := NewColorHex(original)
|
||||||
|
got := c.Hex()
|
||||||
|
if got != original {
|
||||||
|
t.Errorf("hex round-trip: NewColorHex(%q).Hex() = %q", original, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
445
config/colors.go
445
config/colors.go
|
|
@ -1,6 +1,6 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
// Color and style definitions for the UI: gradients, tcell colors, tview color tags.
|
// Color and style definitions for the UI: gradients, unified Color values.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
|
@ -18,218 +18,330 @@ type ColorConfig struct {
|
||||||
CaptionFallbackGradient Gradient
|
CaptionFallbackGradient Gradient
|
||||||
|
|
||||||
// Task box colors
|
// Task box colors
|
||||||
TaskBoxSelectedBackground tcell.Color
|
TaskBoxSelectedBackground Color
|
||||||
TaskBoxSelectedText tcell.Color
|
TaskBoxSelectedText Color
|
||||||
TaskBoxSelectedBorder tcell.Color
|
TaskBoxSelectedBorder Color
|
||||||
TaskBoxUnselectedBorder tcell.Color
|
TaskBoxUnselectedBorder Color
|
||||||
TaskBoxUnselectedBackground tcell.Color
|
TaskBoxUnselectedBackground Color
|
||||||
TaskBoxIDColor Gradient
|
TaskBoxIDColor Gradient
|
||||||
TaskBoxTitleColor string // tview color string like "[#b8b8b8]"
|
TaskBoxTitleColor Color
|
||||||
TaskBoxLabelColor string // tview color string like "[#767676]"
|
TaskBoxLabelColor Color
|
||||||
TaskBoxDescriptionColor string // tview color string like "[#767676]"
|
TaskBoxDescriptionColor Color
|
||||||
TaskBoxTagValueColor string // tview color string like "[#5a6f8f]"
|
TaskBoxTagValueColor Color
|
||||||
TaskListSelectionColor string // tview color string for selected row highlight, e.g. "[white:#3a5f8a]"
|
TaskListSelectionFg Color // selected row foreground
|
||||||
TaskListStatusDoneColor string // tview color string for done status indicator, e.g. "[#00ff7f]"
|
TaskListSelectionBg Color // selected row background
|
||||||
TaskListStatusPendingColor string // tview color string for pending status indicator, e.g. "[white]"
|
TaskListStatusDoneColor Color
|
||||||
|
TaskListStatusPendingColor Color
|
||||||
|
|
||||||
// Task detail view colors
|
// Task detail view colors
|
||||||
TaskDetailIDColor Gradient
|
TaskDetailIDColor Gradient
|
||||||
TaskDetailTitleText string // tview color string like "[yellow]"
|
TaskDetailTitleText Color
|
||||||
TaskDetailLabelText string // tview color string like "[green]"
|
TaskDetailLabelText Color
|
||||||
TaskDetailValueText string // tview color string like "[white]"
|
TaskDetailValueText Color
|
||||||
TaskDetailCommentAuthor string // tview color string like "[yellow]"
|
TaskDetailCommentAuthor Color
|
||||||
TaskDetailEditDimTextColor string // tview color string like "[#808080]"
|
TaskDetailEditDimTextColor Color
|
||||||
TaskDetailEditDimLabelColor string // tview color string like "[#606060]"
|
TaskDetailEditDimLabelColor Color
|
||||||
TaskDetailEditDimValueColor string // tview color string like "[#909090]"
|
TaskDetailEditDimValueColor Color
|
||||||
TaskDetailEditFocusMarker string // tview color string like "[yellow]"
|
TaskDetailEditFocusMarker Color
|
||||||
TaskDetailEditFocusText string // tview color string like "[white]"
|
TaskDetailEditFocusText Color
|
||||||
TaskDetailTagForeground tcell.Color
|
TaskDetailTagForeground Color
|
||||||
TaskDetailTagBackground tcell.Color
|
TaskDetailTagBackground Color
|
||||||
TaskDetailPlaceholderColor tcell.Color
|
TaskDetailPlaceholderColor Color
|
||||||
|
|
||||||
// Content area colors (base canvas for editable/readable content)
|
// Content area colors (base canvas for editable/readable content)
|
||||||
ContentBackgroundColor tcell.Color
|
ContentBackgroundColor Color
|
||||||
ContentTextColor tcell.Color
|
ContentTextColor Color
|
||||||
|
|
||||||
// Search box colors
|
// Search box colors
|
||||||
SearchBoxLabelColor tcell.Color
|
SearchBoxLabelColor Color
|
||||||
SearchBoxBackgroundColor tcell.Color
|
SearchBoxBackgroundColor Color
|
||||||
SearchBoxTextColor tcell.Color
|
SearchBoxTextColor Color
|
||||||
|
|
||||||
// Input field colors (used in task detail edit mode)
|
// Input field colors (used in task detail edit mode)
|
||||||
InputFieldBackgroundColor tcell.Color
|
InputFieldBackgroundColor Color
|
||||||
InputFieldTextColor tcell.Color
|
InputFieldTextColor Color
|
||||||
|
|
||||||
// Completion prompt colors
|
// Completion prompt colors
|
||||||
CompletionHintColor tcell.Color
|
CompletionHintColor Color
|
||||||
|
|
||||||
// Burndown chart colors
|
// Burndown chart colors
|
||||||
BurndownChartAxisColor tcell.Color
|
BurndownChartAxisColor Color
|
||||||
BurndownChartLabelColor tcell.Color
|
BurndownChartLabelColor Color
|
||||||
BurndownChartValueColor tcell.Color
|
BurndownChartValueColor Color
|
||||||
BurndownChartBarColor tcell.Color
|
BurndownChartBarColor Color
|
||||||
BurndownChartGradientFrom Gradient
|
BurndownChartGradientFrom Gradient
|
||||||
BurndownChartGradientTo Gradient
|
BurndownChartGradientTo Gradient
|
||||||
BurndownHeaderGradientFrom Gradient // Header-specific chart gradient
|
BurndownHeaderGradientFrom Gradient // Header-specific chart gradient
|
||||||
BurndownHeaderGradientTo Gradient
|
BurndownHeaderGradientTo Gradient
|
||||||
|
|
||||||
// Header view colors
|
// Header view colors
|
||||||
HeaderInfoLabel string // tview color string for view name (bold)
|
HeaderInfoLabel Color
|
||||||
HeaderInfoSeparator string // tview color string for horizontal rule below name
|
HeaderInfoSeparator Color
|
||||||
HeaderInfoDesc string // tview color string for view description
|
HeaderInfoDesc Color
|
||||||
HeaderKeyBinding string // tview color string like "[yellow]"
|
HeaderKeyBinding Color
|
||||||
HeaderKeyText string // tview color string like "[white]"
|
HeaderKeyText Color
|
||||||
|
|
||||||
// Points visual bar colors
|
// Points visual bar colors
|
||||||
PointsFilledColor string // tview color string for filled segments
|
PointsFilledColor Color
|
||||||
PointsUnfilledColor string // tview color string for unfilled segments
|
PointsUnfilledColor Color
|
||||||
|
|
||||||
// Header context help action colors
|
// Header context help action colors
|
||||||
HeaderActionGlobalKeyColor string // tview color string for global action keys
|
HeaderActionGlobalKeyColor Color
|
||||||
HeaderActionGlobalLabelColor string // tview color string for global action labels
|
HeaderActionGlobalLabelColor Color
|
||||||
HeaderActionPluginKeyColor string // tview color string for plugin action keys
|
HeaderActionPluginKeyColor Color
|
||||||
HeaderActionPluginLabelColor string // tview color string for plugin action labels
|
HeaderActionPluginLabelColor Color
|
||||||
HeaderActionViewKeyColor string // tview color string for view action keys
|
HeaderActionViewKeyColor Color
|
||||||
HeaderActionViewLabelColor string // tview color string for view action labels
|
HeaderActionViewLabelColor Color
|
||||||
|
|
||||||
// Plugin-specific colors
|
// Plugin-specific colors
|
||||||
DepsEditorBackground tcell.Color // muted slate for dependency editor caption
|
DepsEditorBackground Color // muted slate for dependency editor caption
|
||||||
|
|
||||||
// Fallback solid colors for gradient scenarios (used when UseGradients = false)
|
// Fallback solid colors for gradient scenarios (used when UseGradients = false)
|
||||||
FallbackTaskIDColor tcell.Color // Deep Sky Blue (end of task ID gradient)
|
FallbackTaskIDColor Color // Deep Sky Blue (end of task ID gradient)
|
||||||
FallbackBurndownColor tcell.Color // Purple (start of burndown gradient)
|
FallbackBurndownColor Color // Purple (start of burndown gradient)
|
||||||
|
|
||||||
// Statusline colors (bottom bar, powerline style)
|
// Statusline colors (bottom bar, powerline style)
|
||||||
StatuslineBg string // hex color for stat segment background, e.g. "#3a3a5c"
|
StatuslineBg Color
|
||||||
StatuslineFg string // hex color for stat segment text, e.g. "#cccccc"
|
StatuslineFg Color
|
||||||
StatuslineAccentBg string // hex color for accent segment background (first segment), e.g. "#5f87af"
|
StatuslineAccentBg Color
|
||||||
StatuslineAccentFg string // hex color for accent segment text, e.g. "#1c1c2e"
|
StatuslineAccentFg Color
|
||||||
StatuslineInfoFg string // hex color for info message text
|
StatuslineInfoFg Color
|
||||||
StatuslineInfoBg string // hex color for info message background
|
StatuslineInfoBg Color
|
||||||
StatuslineErrorFg string // hex color for error message text
|
StatuslineErrorFg Color
|
||||||
StatuslineErrorBg string // hex color for error message background
|
StatuslineErrorBg Color
|
||||||
StatuslineFillBg string // hex color for empty statusline area between segments
|
StatuslineFillBg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultColors returns the default color configuration
|
// Palette defines the base color values used throughout the UI.
|
||||||
func DefaultColors() *ColorConfig {
|
// Each entry is a semantic name for a unique color; ColorConfig fields reference these.
|
||||||
return &ColorConfig{
|
// To change a color everywhere it appears, change it here.
|
||||||
// Caption fallback gradient
|
type Palette struct {
|
||||||
CaptionFallbackGradient: Gradient{
|
HighlightColor Color // yellow — accents, focus markers, key bindings, borders
|
||||||
Start: [3]int{25, 25, 112}, // Midnight Blue (center)
|
TextColor Color // white — primary text on dark background
|
||||||
End: [3]int{65, 105, 225}, // Royal Blue (edges)
|
TransparentColor Color // default/transparent — inherit background
|
||||||
|
MutedColor Color // #808080 — de-emphasized text, placeholders, hints, unfocused borders
|
||||||
|
SubduedTextColor Color // #767676 — labels, descriptions
|
||||||
|
DimLabelColor Color // #606060 — dimmed labels in edit mode
|
||||||
|
DimValueColor Color // #909090 — dimmed values in edit mode
|
||||||
|
SoftTextColor Color // #b8b8b8 — titles in task boxes
|
||||||
|
AccentColor Color // #008000 — label text (green)
|
||||||
|
ValueColor Color // #8c92ac — field values (cool gray)
|
||||||
|
TagFgColor Color // #b4c8dc — tag chip foreground (light blue-gray)
|
||||||
|
TagBgColor Color // #1e3278 — tag chip background (dark blue)
|
||||||
|
SuccessColor Color // #00ff7f — spring green, done indicator
|
||||||
|
InfoLabelColor Color // #ffa500 — orange, header view name
|
||||||
|
InfoSepColor Color // #555555 — header separator
|
||||||
|
InfoDescColor Color // #888888 — header description
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
SelectionBgColor Color // #3a5f8a — steel blue selection row background
|
||||||
|
SelectionFgColor Color // ANSI 33 blue — selected task box background
|
||||||
|
SelectionText Color // ANSI 117 — selected task box text
|
||||||
|
|
||||||
|
// Tags (task box inline)
|
||||||
|
TagValueColor Color // #5a6f8f — blueish gray for inline tag values
|
||||||
|
|
||||||
|
// Action key colors (header context help)
|
||||||
|
ActionKeyColor Color // #ff8c00 — orange for plugin action keys
|
||||||
|
ActionLabelColor Color // #b0b0b0 — light gray for plugin action labels
|
||||||
|
ViewActionKeyColor Color // #5fafff — cyan for view-specific action keys
|
||||||
|
|
||||||
|
// Points bar
|
||||||
|
PointsFilledColor Color // #508cff — blue filled segments
|
||||||
|
PointsUnfilledColor Color // #5f6982 — gray unfilled segments
|
||||||
|
|
||||||
|
// Chart
|
||||||
|
ChartAxisColor Color // #505050 — dark gray chart axis
|
||||||
|
ChartLabelColor Color // #c8c8c8 — light gray chart labels
|
||||||
|
ChartValueColor Color // #ebebeb — very light gray chart values
|
||||||
|
ChartBarColor Color // #78aaff — light blue chart bars
|
||||||
|
|
||||||
|
// Gradients (not Color, but part of the palette)
|
||||||
|
IDGradient Gradient // Dodger Blue → Deep Sky Blue
|
||||||
|
CaptionFallbackGradient Gradient // Midnight Blue → Royal Blue
|
||||||
|
DeepSkyBlue Color // #00bfff — fallback for ID gradient
|
||||||
|
DeepPurple Color // #865ad6 — fallback for burndown gradient
|
||||||
|
|
||||||
|
// Plugin-specific
|
||||||
|
DepsEditorBgColor Color // #4e5768 — muted slate
|
||||||
|
|
||||||
|
// Statusline (Nord palette)
|
||||||
|
NordPolarNight1 Color // #2e3440
|
||||||
|
NordPolarNight2 Color // #3b4252
|
||||||
|
NordPolarNight3 Color // #434c5e
|
||||||
|
NordSnowStorm1 Color // #d8dee9
|
||||||
|
NordFrostBlue Color // #5e81ac
|
||||||
|
NordAuroraGreen Color // #a3be8c
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPalette returns the default color palette.
|
||||||
|
func DefaultPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#ffff00"),
|
||||||
|
TextColor: NewColorHex("#ffffff"),
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#808080"),
|
||||||
|
SubduedTextColor: NewColorHex("#767676"),
|
||||||
|
DimLabelColor: NewColorHex("#606060"),
|
||||||
|
DimValueColor: NewColorHex("#909090"),
|
||||||
|
SoftTextColor: NewColorHex("#b8b8b8"),
|
||||||
|
AccentColor: NewColor(tcell.ColorGreen),
|
||||||
|
ValueColor: NewColorHex("#8c92ac"),
|
||||||
|
TagFgColor: NewColorRGB(180, 200, 220),
|
||||||
|
TagBgColor: NewColorRGB(30, 50, 120),
|
||||||
|
SuccessColor: NewColorHex("#00ff7f"),
|
||||||
|
InfoLabelColor: NewColorHex("#ffa500"),
|
||||||
|
InfoSepColor: NewColorHex("#555555"),
|
||||||
|
InfoDescColor: NewColorHex("#888888"),
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#3a5f8a"),
|
||||||
|
SelectionFgColor: NewColor(tcell.PaletteColor(33)),
|
||||||
|
SelectionText: NewColor(tcell.PaletteColor(117)),
|
||||||
|
|
||||||
|
TagValueColor: NewColorHex("#5a6f8f"),
|
||||||
|
|
||||||
|
ActionKeyColor: NewColorHex("#ff8c00"),
|
||||||
|
ActionLabelColor: NewColorHex("#b0b0b0"),
|
||||||
|
ViewActionKeyColor: NewColorHex("#5fafff"),
|
||||||
|
|
||||||
|
PointsFilledColor: NewColorHex("#508cff"),
|
||||||
|
PointsUnfilledColor: NewColorHex("#5f6982"),
|
||||||
|
|
||||||
|
ChartAxisColor: NewColorRGB(80, 80, 80),
|
||||||
|
ChartLabelColor: NewColorRGB(200, 200, 200),
|
||||||
|
ChartValueColor: NewColorRGB(235, 235, 235),
|
||||||
|
ChartBarColor: NewColorRGB(120, 170, 255),
|
||||||
|
|
||||||
|
IDGradient: Gradient{
|
||||||
|
Start: [3]int{30, 144, 255},
|
||||||
|
End: [3]int{0, 191, 255},
|
||||||
},
|
},
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{25, 25, 112},
|
||||||
|
End: [3]int{65, 105, 225},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorRGB(0, 191, 255),
|
||||||
|
DeepPurple: NewColorRGB(134, 90, 214),
|
||||||
|
|
||||||
|
DepsEditorBgColor: NewColorHex("#4e5768"),
|
||||||
|
|
||||||
|
NordPolarNight1: NewColorHex("#2e3440"),
|
||||||
|
NordPolarNight2: NewColorHex("#3b4252"),
|
||||||
|
NordPolarNight3: NewColorHex("#434c5e"),
|
||||||
|
NordSnowStorm1: NewColorHex("#d8dee9"),
|
||||||
|
NordFrostBlue: NewColorHex("#5e81ac"),
|
||||||
|
NordAuroraGreen: NewColorHex("#a3be8c"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultColors returns the default color configuration built from the default palette.
|
||||||
|
func DefaultColors() *ColorConfig {
|
||||||
|
return ColorsFromPalette(DefaultPalette())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorsFromPalette builds a ColorConfig from a Palette.
|
||||||
|
func ColorsFromPalette(p Palette) *ColorConfig {
|
||||||
|
deepPurpleSolid := Gradient{Start: [3]int{134, 90, 214}, End: [3]int{134, 90, 214}}
|
||||||
|
blueCyanSolid := Gradient{Start: [3]int{90, 170, 255}, End: [3]int{90, 170, 255}}
|
||||||
|
headerPurpleSolid := Gradient{Start: [3]int{160, 120, 230}, End: [3]int{160, 120, 230}}
|
||||||
|
headerCyanSolid := Gradient{Start: [3]int{110, 190, 255}, End: [3]int{110, 190, 255}}
|
||||||
|
|
||||||
|
return &ColorConfig{
|
||||||
|
CaptionFallbackGradient: p.CaptionFallbackGradient,
|
||||||
|
|
||||||
// Task box
|
// Task box
|
||||||
TaskBoxSelectedBackground: tcell.PaletteColor(33), // Blue (ANSI 33)
|
TaskBoxSelectedBackground: p.SelectionFgColor,
|
||||||
TaskBoxSelectedText: tcell.PaletteColor(117), // Light Blue (ANSI 117)
|
TaskBoxSelectedText: p.SelectionText,
|
||||||
TaskBoxSelectedBorder: tcell.ColorYellow,
|
TaskBoxSelectedBorder: p.HighlightColor,
|
||||||
TaskBoxUnselectedBorder: tcell.ColorGray,
|
TaskBoxUnselectedBorder: p.MutedColor,
|
||||||
TaskBoxUnselectedBackground: tcell.ColorDefault, // transparent/no background
|
TaskBoxUnselectedBackground: p.TransparentColor,
|
||||||
TaskBoxIDColor: Gradient{
|
TaskBoxIDColor: p.IDGradient,
|
||||||
Start: [3]int{30, 144, 255}, // Dodger Blue
|
TaskBoxTitleColor: p.SoftTextColor,
|
||||||
End: [3]int{0, 191, 255}, // Deep Sky Blue
|
TaskBoxLabelColor: p.SubduedTextColor,
|
||||||
},
|
TaskBoxDescriptionColor: p.SubduedTextColor,
|
||||||
TaskBoxTitleColor: "[#b8b8b8]", // Light gray
|
TaskBoxTagValueColor: p.TagValueColor,
|
||||||
TaskBoxLabelColor: "[#767676]", // Darker gray for labels
|
TaskListSelectionFg: p.TextColor,
|
||||||
TaskBoxDescriptionColor: "[#767676]", // Darker gray for description
|
TaskListSelectionBg: p.SelectionBgColor,
|
||||||
TaskBoxTagValueColor: "[#5a6f8f]", // Blueish gray for tag values
|
TaskListStatusDoneColor: p.SuccessColor,
|
||||||
TaskListSelectionColor: "[white:#3a5f8a]", // White text on steel blue background
|
TaskListStatusPendingColor: p.TextColor,
|
||||||
TaskListStatusDoneColor: "[#00ff7f]", // Spring green for done checkmark
|
|
||||||
TaskListStatusPendingColor: "[white]", // White for pending circle
|
|
||||||
|
|
||||||
// Task detail
|
// Task detail
|
||||||
TaskDetailIDColor: Gradient{
|
TaskDetailIDColor: p.IDGradient,
|
||||||
Start: [3]int{30, 144, 255}, // Dodger Blue (same as task box)
|
TaskDetailTitleText: p.HighlightColor,
|
||||||
End: [3]int{0, 191, 255}, // Deep Sky Blue
|
TaskDetailLabelText: p.AccentColor,
|
||||||
},
|
TaskDetailValueText: p.ValueColor,
|
||||||
TaskDetailTitleText: "[yellow]",
|
TaskDetailCommentAuthor: p.HighlightColor,
|
||||||
TaskDetailLabelText: "[green]",
|
TaskDetailEditDimTextColor: p.MutedColor,
|
||||||
TaskDetailValueText: "[#8c92ac]",
|
TaskDetailEditDimLabelColor: p.DimLabelColor,
|
||||||
TaskDetailCommentAuthor: "[yellow]",
|
TaskDetailEditDimValueColor: p.DimValueColor,
|
||||||
TaskDetailEditDimTextColor: "[#808080]", // Medium gray for dim text
|
TaskDetailEditFocusMarker: p.HighlightColor,
|
||||||
TaskDetailEditDimLabelColor: "[#606060]", // Darker gray for dim labels
|
TaskDetailEditFocusText: p.TextColor,
|
||||||
TaskDetailEditDimValueColor: "[#909090]", // Lighter gray for dim values
|
TaskDetailTagForeground: p.TagFgColor,
|
||||||
TaskDetailEditFocusMarker: "[yellow]", // Yellow arrow for focus
|
TaskDetailTagBackground: p.TagBgColor,
|
||||||
TaskDetailEditFocusText: "[white]", // White text after arrow
|
TaskDetailPlaceholderColor: p.MutedColor,
|
||||||
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)
|
// Content area
|
||||||
ContentBackgroundColor: tcell.ColorBlack, // dark theme: explicit black
|
ContentBackgroundColor: NewColor(tcell.ColorBlack),
|
||||||
ContentTextColor: tcell.ColorWhite, // dark theme: white text
|
ContentTextColor: p.TextColor,
|
||||||
|
|
||||||
// Search box
|
// Search box
|
||||||
SearchBoxLabelColor: tcell.ColorWhite,
|
SearchBoxLabelColor: p.TextColor,
|
||||||
SearchBoxBackgroundColor: tcell.ColorDefault, // Transparent
|
SearchBoxBackgroundColor: p.TransparentColor,
|
||||||
SearchBoxTextColor: tcell.ColorWhite,
|
SearchBoxTextColor: p.TextColor,
|
||||||
|
|
||||||
// Input field colors
|
// Input field
|
||||||
InputFieldBackgroundColor: tcell.ColorDefault, // Transparent
|
InputFieldBackgroundColor: p.TransparentColor,
|
||||||
InputFieldTextColor: tcell.ColorWhite,
|
InputFieldTextColor: p.TextColor,
|
||||||
|
|
||||||
// Completion prompt
|
// Completion prompt
|
||||||
CompletionHintColor: tcell.NewRGBColor(128, 128, 128), // Medium gray for hint text
|
CompletionHintColor: p.MutedColor,
|
||||||
|
|
||||||
// Burndown chart
|
// Burndown chart
|
||||||
BurndownChartAxisColor: tcell.NewRGBColor(80, 80, 80), // Dark gray
|
BurndownChartAxisColor: p.ChartAxisColor,
|
||||||
BurndownChartLabelColor: tcell.NewRGBColor(200, 200, 200), // Light gray
|
BurndownChartLabelColor: p.ChartLabelColor,
|
||||||
BurndownChartValueColor: tcell.NewRGBColor(235, 235, 235), // Very light gray
|
BurndownChartValueColor: p.ChartValueColor,
|
||||||
BurndownChartBarColor: tcell.NewRGBColor(120, 170, 255), // Light blue
|
BurndownChartBarColor: p.ChartBarColor,
|
||||||
BurndownChartGradientFrom: Gradient{
|
BurndownChartGradientFrom: deepPurpleSolid,
|
||||||
Start: [3]int{134, 90, 214}, // Deep purple
|
BurndownChartGradientTo: blueCyanSolid,
|
||||||
End: [3]int{134, 90, 214}, // Deep purple (solid, not gradient)
|
BurndownHeaderGradientFrom: headerPurpleSolid,
|
||||||
},
|
BurndownHeaderGradientTo: headerCyanSolid,
|
||||||
BurndownChartGradientTo: Gradient{
|
|
||||||
Start: [3]int{90, 170, 255}, // Blue/cyan
|
|
||||||
End: [3]int{90, 170, 255}, // Blue/cyan (solid, not gradient)
|
|
||||||
},
|
|
||||||
BurndownHeaderGradientFrom: Gradient{
|
|
||||||
Start: [3]int{160, 120, 230}, // Purple base for header chart
|
|
||||||
End: [3]int{160, 120, 230}, // Purple base (solid)
|
|
||||||
},
|
|
||||||
BurndownHeaderGradientTo: Gradient{
|
|
||||||
Start: [3]int{110, 190, 255}, // Cyan top for header chart
|
|
||||||
End: [3]int{110, 190, 255}, // Cyan top (solid)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Points visual bar
|
// Points bar
|
||||||
PointsFilledColor: "[#508cff]", // Blue for filled segments
|
PointsFilledColor: p.PointsFilledColor,
|
||||||
PointsUnfilledColor: "[#5f6982]", // Gray for unfilled segments
|
PointsUnfilledColor: p.PointsUnfilledColor,
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
HeaderInfoLabel: "[orange]",
|
HeaderInfoLabel: p.InfoLabelColor,
|
||||||
HeaderInfoSeparator: "[#555555]",
|
HeaderInfoSeparator: p.InfoSepColor,
|
||||||
HeaderInfoDesc: "[#888888]",
|
HeaderInfoDesc: p.InfoDescColor,
|
||||||
HeaderKeyBinding: "[yellow]",
|
HeaderKeyBinding: p.HighlightColor,
|
||||||
HeaderKeyText: "[white]",
|
HeaderKeyText: p.TextColor,
|
||||||
|
|
||||||
// Header context help actions
|
// Header context help actions
|
||||||
HeaderActionGlobalKeyColor: "#ffff00", // yellow for global actions
|
HeaderActionGlobalKeyColor: p.HighlightColor,
|
||||||
HeaderActionGlobalLabelColor: "#ffffff", // white for global action labels
|
HeaderActionGlobalLabelColor: p.TextColor,
|
||||||
HeaderActionPluginKeyColor: "#ff8c00", // orange for plugin actions
|
HeaderActionPluginKeyColor: p.ActionKeyColor,
|
||||||
HeaderActionPluginLabelColor: "#b0b0b0", // light gray for plugin labels
|
HeaderActionPluginLabelColor: p.ActionLabelColor,
|
||||||
HeaderActionViewKeyColor: "#5fafff", // cyan for view-specific actions
|
HeaderActionViewKeyColor: p.ViewActionKeyColor,
|
||||||
HeaderActionViewLabelColor: "#808080", // gray for view-specific labels
|
HeaderActionViewLabelColor: p.MutedColor,
|
||||||
|
|
||||||
// Plugin-specific
|
// Plugin-specific
|
||||||
DepsEditorBackground: tcell.NewHexColor(0x4e5768), // Muted slate
|
DepsEditorBackground: p.DepsEditorBgColor,
|
||||||
|
|
||||||
// Fallback solid colors (no-gradient terminals)
|
// Fallback solid colors
|
||||||
FallbackTaskIDColor: tcell.NewRGBColor(0, 191, 255), // Deep Sky Blue
|
FallbackTaskIDColor: p.DeepSkyBlue,
|
||||||
FallbackBurndownColor: tcell.NewRGBColor(134, 90, 214), // Purple
|
FallbackBurndownColor: p.DeepPurple,
|
||||||
|
|
||||||
// Statusline (Nord theme)
|
// Statusline
|
||||||
StatuslineBg: "#434c5e", // Nord polar night 3
|
StatuslineBg: p.NordPolarNight3,
|
||||||
StatuslineFg: "#d8dee9", // Nord snow storm 1
|
StatuslineFg: p.NordSnowStorm1,
|
||||||
StatuslineAccentBg: "#5e81ac", // Nord frost blue
|
StatuslineAccentBg: p.NordFrostBlue,
|
||||||
StatuslineAccentFg: "#2e3440", // Nord polar night 1
|
StatuslineAccentFg: p.NordPolarNight1,
|
||||||
StatuslineInfoFg: "#a3be8c", // Nord aurora green
|
StatuslineInfoFg: p.NordAuroraGreen,
|
||||||
StatuslineInfoBg: "#3b4252", // Nord polar night 2
|
StatuslineInfoBg: p.NordPolarNight2,
|
||||||
StatuslineErrorFg: "#ffff00", // yellow, matches header global key color
|
StatuslineErrorFg: p.HighlightColor,
|
||||||
StatuslineErrorBg: "#3b4252", // Nord polar night 2
|
StatuslineErrorBg: p.NordPolarNight2,
|
||||||
StatuslineFillBg: "#3b4252", // Nord polar night 2
|
StatuslineFillBg: p.NordPolarNight2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,13 +363,14 @@ func GetColors() *ColorConfig {
|
||||||
globalColors = DefaultColors()
|
globalColors = DefaultColors()
|
||||||
// Apply theme-aware overrides for critical text colors
|
// Apply theme-aware overrides for critical text colors
|
||||||
if GetEffectiveTheme() == "light" {
|
if GetEffectiveTheme() == "light" {
|
||||||
globalColors.ContentBackgroundColor = tcell.ColorDefault
|
black := NewColor(tcell.ColorBlack)
|
||||||
globalColors.ContentTextColor = tcell.ColorBlack
|
globalColors.ContentBackgroundColor = DefaultColor()
|
||||||
globalColors.SearchBoxLabelColor = tcell.ColorBlack
|
globalColors.ContentTextColor = black
|
||||||
globalColors.SearchBoxTextColor = tcell.ColorBlack
|
globalColors.SearchBoxLabelColor = black
|
||||||
globalColors.InputFieldTextColor = tcell.ColorBlack
|
globalColors.SearchBoxTextColor = black
|
||||||
globalColors.TaskDetailEditFocusText = "[black]"
|
globalColors.InputFieldTextColor = black
|
||||||
globalColors.HeaderKeyText = "[black]"
|
globalColors.TaskDetailEditFocusText = black
|
||||||
|
globalColors.HeaderKeyText = black
|
||||||
}
|
}
|
||||||
colorsInitialized = true
|
colorsInitialized = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -381,9 +381,10 @@ func (c *TaskEditCoordinator) prepareView(activeView View, focus model.EditField
|
||||||
_ = c.FocusNextField(activeView)
|
_ = c.FocusNextField(activeView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rejectionMessage extracts clean rejection reasons from an error.
|
// rejectionMessage extracts a clean user-facing message from an error.
|
||||||
// If the error wraps a RejectionError, returns just the reasons without
|
// For RejectionError: returns just the rejection reasons.
|
||||||
// the "failed to update task:" / "validation failed:" prefixes.
|
// For other errors: unwraps to the root cause to strip wrapper prefixes
|
||||||
|
// like "failed to update task: failed to save task:".
|
||||||
func rejectionMessage(err error) string {
|
func rejectionMessage(err error) string {
|
||||||
var re *service.RejectionError
|
var re *service.RejectionError
|
||||||
if errors.As(err, &re) {
|
if errors.As(err, &re) {
|
||||||
|
|
@ -393,5 +394,13 @@ func rejectionMessage(err error) string {
|
||||||
}
|
}
|
||||||
return strings.Join(reasons, "; ")
|
return strings.Join(reasons, "; ")
|
||||||
}
|
}
|
||||||
|
// unwrap to the innermost error for a clean message
|
||||||
|
for {
|
||||||
|
inner := errors.Unwrap(err)
|
||||||
|
if inner == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = inner
|
||||||
|
}
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
|
||||||
|
"github.com/boolean-maybe/tiki/config"
|
||||||
"github.com/boolean-maybe/tiki/ruki"
|
"github.com/boolean-maybe/tiki/ruki"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,8 +25,8 @@ type BasePlugin struct {
|
||||||
Key tcell.Key // tcell key constant (e.g. KeyCtrlH)
|
Key tcell.Key // tcell key constant (e.g. KeyCtrlH)
|
||||||
Rune rune // printable character (e.g. 'L')
|
Rune rune // printable character (e.g. 'L')
|
||||||
Modifier tcell.ModMask // modifier keys (Alt, Shift, Ctrl, etc.)
|
Modifier tcell.ModMask // modifier keys (Alt, Shift, Ctrl, etc.)
|
||||||
Foreground tcell.Color // caption text color
|
Foreground config.Color // caption text color
|
||||||
Background tcell.Color // caption background color
|
Background config.Color // caption background color
|
||||||
FilePath string // source file path (for error messages)
|
FilePath string // source file path (for error messages)
|
||||||
ConfigIndex int // index in workflow.yaml views array (-1 if not from a config file)
|
ConfigIndex int // index in workflow.yaml views array (-1 if not from a config file)
|
||||||
Type string // plugin type: "tiki" or "doki"
|
Type string // plugin type: "tiki" or "doki"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pluginFileConfig represents the YAML structure of a plugin file
|
// pluginFileConfig represents the YAML structure of a plugin file
|
||||||
type pluginFileConfig struct {
|
type pluginFileConfig struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
|
|
@ -58,10 +54,10 @@ func mergePluginDefinitions(base Plugin, override Plugin) Plugin {
|
||||||
result.Rune = overrideTiki.Rune
|
result.Rune = overrideTiki.Rune
|
||||||
result.Modifier = overrideTiki.Modifier
|
result.Modifier = overrideTiki.Modifier
|
||||||
}
|
}
|
||||||
if overrideTiki.Foreground != tcell.ColorDefault {
|
if !overrideTiki.Foreground.IsDefault() {
|
||||||
result.Foreground = overrideTiki.Foreground
|
result.Foreground = overrideTiki.Foreground
|
||||||
}
|
}
|
||||||
if overrideTiki.Background != tcell.ColorDefault {
|
if !overrideTiki.Background.IsDefault() {
|
||||||
result.Background = overrideTiki.Background
|
result.Background = overrideTiki.Background
|
||||||
}
|
}
|
||||||
if len(overrideTiki.Lanes) > 0 {
|
if len(overrideTiki.Lanes) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
|
||||||
|
"github.com/boolean-maybe/tiki/config"
|
||||||
rukiRuntime "github.com/boolean-maybe/tiki/internal/ruki/runtime"
|
rukiRuntime "github.com/boolean-maybe/tiki/internal/ruki/runtime"
|
||||||
"github.com/boolean-maybe/tiki/ruki"
|
"github.com/boolean-maybe/tiki/ruki"
|
||||||
)
|
)
|
||||||
|
|
@ -30,8 +31,8 @@ func TestMergePluginDefinitions_TikiToTiki(t *testing.T) {
|
||||||
Key: tcell.KeyRune,
|
Key: tcell.KeyRune,
|
||||||
Rune: 'B',
|
Rune: 'B',
|
||||||
Modifier: 0,
|
Modifier: 0,
|
||||||
Foreground: tcell.ColorRed,
|
Foreground: config.NewColor(tcell.ColorRed),
|
||||||
Background: tcell.ColorBlue,
|
Background: config.NewColor(tcell.ColorBlue),
|
||||||
Type: "tiki",
|
Type: "tiki",
|
||||||
},
|
},
|
||||||
Lanes: []TikiLane{
|
Lanes: []TikiLane{
|
||||||
|
|
@ -47,8 +48,8 @@ func TestMergePluginDefinitions_TikiToTiki(t *testing.T) {
|
||||||
Key: tcell.KeyRune,
|
Key: tcell.KeyRune,
|
||||||
Rune: 'O',
|
Rune: 'O',
|
||||||
Modifier: tcell.ModAlt,
|
Modifier: tcell.ModAlt,
|
||||||
Foreground: tcell.ColorGreen,
|
Foreground: config.NewColor(tcell.ColorGreen),
|
||||||
Background: tcell.ColorDefault,
|
Background: config.DefaultColor(),
|
||||||
FilePath: "override.yaml",
|
FilePath: "override.yaml",
|
||||||
ConfigIndex: 1,
|
ConfigIndex: 1,
|
||||||
Type: "tiki",
|
Type: "tiki",
|
||||||
|
|
@ -72,7 +73,7 @@ func TestMergePluginDefinitions_TikiToTiki(t *testing.T) {
|
||||||
if resultTiki.Modifier != tcell.ModAlt {
|
if resultTiki.Modifier != tcell.ModAlt {
|
||||||
t.Errorf("expected ModAlt, got %v", resultTiki.Modifier)
|
t.Errorf("expected ModAlt, got %v", resultTiki.Modifier)
|
||||||
}
|
}
|
||||||
if resultTiki.Foreground != tcell.ColorGreen {
|
if resultTiki.Foreground.TCell() != tcell.ColorGreen {
|
||||||
t.Errorf("expected green foreground, got %v", resultTiki.Foreground)
|
t.Errorf("expected green foreground, got %v", resultTiki.Foreground)
|
||||||
}
|
}
|
||||||
if resultTiki.ViewMode != "expanded" {
|
if resultTiki.ViewMode != "expanded" {
|
||||||
|
|
@ -92,8 +93,8 @@ func TestMergePluginDefinitions_PreservesModifier(t *testing.T) {
|
||||||
Key: tcell.KeyRune,
|
Key: tcell.KeyRune,
|
||||||
Rune: 'M',
|
Rune: 'M',
|
||||||
Modifier: tcell.ModAlt, // this should be preserved
|
Modifier: tcell.ModAlt, // this should be preserved
|
||||||
Foreground: tcell.ColorWhite,
|
Foreground: config.NewColor(tcell.ColorWhite),
|
||||||
Background: tcell.ColorDefault,
|
Background: config.DefaultColor(),
|
||||||
Type: "tiki",
|
Type: "tiki",
|
||||||
},
|
},
|
||||||
Lanes: []TikiLane{
|
Lanes: []TikiLane{
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/boolean-maybe/tiki/config"
|
||||||
"github.com/boolean-maybe/tiki/ruki"
|
"github.com/boolean-maybe/tiki/ruki"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,8 +21,8 @@ func parsePluginConfig(cfg pluginFileConfig, source string, schema ruki.Schema)
|
||||||
|
|
||||||
// Common fields
|
// Common fields
|
||||||
// Use ColorDefault as sentinel so views can detect "not specified" and use theme-appropriate colors
|
// Use ColorDefault as sentinel so views can detect "not specified" and use theme-appropriate colors
|
||||||
fg := parseColor(cfg.Foreground, tcell.ColorDefault)
|
fg := config.NewColor(parseColor(cfg.Foreground, tcell.ColorDefault))
|
||||||
bg := parseColor(cfg.Background, tcell.ColorDefault)
|
bg := config.NewColor(parseColor(cfg.Background, tcell.ColorDefault))
|
||||||
|
|
||||||
key, r, mod, err := parseKey(cfg.Key)
|
key, r, mod, err := parseKey(cfg.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ func RenderGradientText(text string, gradient config.Gradient) string {
|
||||||
|
|
||||||
// RenderAdaptiveGradientText renders text with gradient or solid color based on config.UseGradients.
|
// RenderAdaptiveGradientText renders text with gradient or solid color based on config.UseGradients.
|
||||||
// When gradients are disabled, uses the gradient's end color as a solid color fallback.
|
// When gradients are disabled, uses the gradient's end color as a solid color fallback.
|
||||||
func RenderAdaptiveGradientText(text string, gradient config.Gradient, fallbackColor tcell.Color) string {
|
func RenderAdaptiveGradientText(text string, gradient config.Gradient, fallbackColor config.Color) string {
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +136,7 @@ func RenderAdaptiveGradientText(text string, gradient config.Gradient, fallbackC
|
||||||
}
|
}
|
||||||
|
|
||||||
// GradientFromColor derives a gradient by lightening the base color.
|
// GradientFromColor derives a gradient by lightening the base color.
|
||||||
func GradientFromColor(primary tcell.Color, ratio float64, fallback config.Gradient) config.Gradient {
|
func GradientFromColor(primary config.Color, ratio float64, fallback config.Gradient) config.Gradient {
|
||||||
r, g, b := primary.RGB()
|
r, g, b := primary.RGB()
|
||||||
if r == 0 && g == 0 && b == 0 {
|
if r == 0 && g == 0 && b == 0 {
|
||||||
return fallback
|
return fallback
|
||||||
|
|
@ -152,7 +152,7 @@ func GradientFromColor(primary tcell.Color, ratio float64, fallback config.Gradi
|
||||||
}
|
}
|
||||||
|
|
||||||
// GradientFromColorVibrant derives a vibrant gradient by boosting RGB values.
|
// GradientFromColorVibrant derives a vibrant gradient by boosting RGB values.
|
||||||
func GradientFromColorVibrant(primary tcell.Color, boost float64, fallback config.Gradient) config.Gradient {
|
func GradientFromColorVibrant(primary config.Color, boost float64, fallback config.Gradient) config.Gradient {
|
||||||
r, g, b := primary.RGB()
|
r, g, b := primary.RGB()
|
||||||
if r == 0 && g == 0 && b == 0 {
|
if r == 0 && g == 0 && b == 0 {
|
||||||
return fallback
|
return fallback
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/boolean-maybe/tiki/config"
|
"github.com/boolean-maybe/tiki/config"
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInterpolateRGB(t *testing.T) {
|
func TestInterpolateRGB(t *testing.T) {
|
||||||
|
|
@ -227,7 +226,7 @@ func TestGradientFromColor(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("black color uses fallback", func(t *testing.T) {
|
t.Run("black color uses fallback", func(t *testing.T) {
|
||||||
black := tcell.NewRGBColor(0, 0, 0)
|
black := config.NewColorRGB(0, 0, 0)
|
||||||
got := GradientFromColor(black, 0.5, fallback)
|
got := GradientFromColor(black, 0.5, fallback)
|
||||||
if got != fallback {
|
if got != fallback {
|
||||||
t.Errorf("GradientFromColor(black) should return fallback, got %v", got)
|
t.Errorf("GradientFromColor(black) should return fallback, got %v", got)
|
||||||
|
|
@ -235,7 +234,7 @@ func TestGradientFromColor(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("non-black color creates gradient", func(t *testing.T) {
|
t.Run("non-black color creates gradient", func(t *testing.T) {
|
||||||
blue := tcell.NewRGBColor(0, 0, 200)
|
blue := config.NewColorRGB(0, 0, 200)
|
||||||
got := GradientFromColor(blue, 0.5, fallback)
|
got := GradientFromColor(blue, 0.5, fallback)
|
||||||
|
|
||||||
// Should have base color and lighter version
|
// Should have base color and lighter version
|
||||||
|
|
@ -257,7 +256,7 @@ func TestGradientFromColorVibrant(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("black color uses fallback", func(t *testing.T) {
|
t.Run("black color uses fallback", func(t *testing.T) {
|
||||||
black := tcell.NewRGBColor(0, 0, 0)
|
black := config.NewColorRGB(0, 0, 0)
|
||||||
got := GradientFromColorVibrant(black, 1.5, fallback)
|
got := GradientFromColorVibrant(black, 1.5, fallback)
|
||||||
if got != fallback {
|
if got != fallback {
|
||||||
t.Errorf("GradientFromColorVibrant(black) should return fallback, got %v", got)
|
t.Errorf("GradientFromColorVibrant(black) should return fallback, got %v", got)
|
||||||
|
|
@ -265,7 +264,7 @@ func TestGradientFromColorVibrant(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("non-black color creates boosted gradient", func(t *testing.T) {
|
t.Run("non-black color creates boosted gradient", func(t *testing.T) {
|
||||||
blue := tcell.NewRGBColor(0, 0, 100)
|
blue := config.NewColorRGB(0, 0, 100)
|
||||||
got := GradientFromColorVibrant(blue, 1.5, fallback)
|
got := GradientFromColorVibrant(blue, 1.5, fallback)
|
||||||
|
|
||||||
// Should have base color and boosted version
|
// Should have base color and boosted version
|
||||||
|
|
@ -301,7 +300,7 @@ func TestRenderAdaptiveGradientText(t *testing.T) {
|
||||||
Start: [3]int{30, 144, 255}, // Dodger Blue
|
Start: [3]int{30, 144, 255}, // Dodger Blue
|
||||||
End: [3]int{0, 191, 255}, // Deep Sky Blue
|
End: [3]int{0, 191, 255}, // Deep Sky Blue
|
||||||
}
|
}
|
||||||
fallback := tcell.NewRGBColor(0, 191, 255) // Deep Sky Blue
|
fallback := config.NewColorRGB(0, 191, 255) // Deep Sky Blue
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -427,15 +426,15 @@ func TestAdaptiveGradientRespectConfig(t *testing.T) {
|
||||||
Start: [3]int{100, 100, 100},
|
Start: [3]int{100, 100, 100},
|
||||||
End: [3]int{200, 200, 200},
|
End: [3]int{200, 200, 200},
|
||||||
}
|
}
|
||||||
fallback := tcell.NewRGBColor(200, 200, 200)
|
fallbackColor := config.NewColorRGB(200, 200, 200)
|
||||||
text := "Test"
|
text := "Test"
|
||||||
|
|
||||||
// Test toggle behavior
|
// Test toggle behavior
|
||||||
config.UseGradients = true
|
config.UseGradients = true
|
||||||
resultWithGradients := RenderAdaptiveGradientText(text, gradient, fallback)
|
resultWithGradients := RenderAdaptiveGradientText(text, gradient, fallbackColor)
|
||||||
|
|
||||||
config.UseGradients = false
|
config.UseGradients = false
|
||||||
resultWithoutGradients := RenderAdaptiveGradientText(text, gradient, fallback)
|
resultWithoutGradients := RenderAdaptiveGradientText(text, gradient, fallbackColor)
|
||||||
|
|
||||||
// Results should be different
|
// Results should be different
|
||||||
if resultWithGradients == resultWithoutGradients {
|
if resultWithGradients == resultWithoutGradients {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/boolean-maybe/tiki/config"
|
||||||
|
)
|
||||||
|
|
||||||
// GeneratePointsVisual formats points as a visual representation using a styled bar.
|
// GeneratePointsVisual formats points as a visual representation using a styled bar.
|
||||||
// Points are scaled to a 0-10 display range based on maxPoints configuration.
|
// Points are scaled to a 0-10 display range based on maxPoints configuration.
|
||||||
|
|
@ -8,8 +12,8 @@ import "strings"
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - points: The task's point value
|
// - points: The task's point value
|
||||||
// - maxPoints: The configured maximum points value (for scaling)
|
// - maxPoints: The configured maximum points value (for scaling)
|
||||||
// - filledColor: tview color tag for filled segments (e.g. "[#508cff]")
|
// - filledColor: Color for filled segments
|
||||||
// - unfilledColor: tview color tag for unfilled segments (e.g. "[#5f6982]")
|
// - unfilledColor: Color for unfilled segments
|
||||||
//
|
//
|
||||||
// Returns: A string with colored filled (❚) and unfilled (❘) segments representing the points value.
|
// Returns: A string with colored filled (❚) and unfilled (❘) segments representing the points value.
|
||||||
//
|
//
|
||||||
|
|
@ -17,8 +21,8 @@ import "strings"
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// GeneratePointsVisual(7, 10, "[#508cff]", "[#5f6982]") returns a bar with 7 blue segments and 3 gray segments
|
// GeneratePointsVisual(7, 10, filledColor, unfilledColor) returns a bar with 7 blue segments and 3 gray segments
|
||||||
func GeneratePointsVisual(points int, maxPoints int, filledColor string, unfilledColor string) string {
|
func GeneratePointsVisual(points int, maxPoints int, filledColor config.Color, unfilledColor config.Color) string {
|
||||||
const displaySegments = 10
|
const displaySegments = 10
|
||||||
const filledChar = "❚"
|
const filledChar = "❚"
|
||||||
const unfilledChar = "❘"
|
const unfilledChar = "❘"
|
||||||
|
|
@ -36,7 +40,7 @@ func GeneratePointsVisual(points int, maxPoints int, filledColor string, unfille
|
||||||
|
|
||||||
// Add filled segments
|
// Add filled segments
|
||||||
if displayPoints > 0 {
|
if displayPoints > 0 {
|
||||||
result.WriteString(filledColor)
|
result.WriteString(filledColor.Tag().String())
|
||||||
for i := 0; i < displayPoints; i++ {
|
for i := 0; i < displayPoints; i++ {
|
||||||
result.WriteString(filledChar)
|
result.WriteString(filledChar)
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +48,7 @@ func GeneratePointsVisual(points int, maxPoints int, filledColor string, unfille
|
||||||
|
|
||||||
// Add unfilled segments
|
// Add unfilled segments
|
||||||
if displayPoints < displaySegments {
|
if displayPoints < displaySegments {
|
||||||
result.WriteString(unfilledColor)
|
result.WriteString(unfilledColor.Tag().String())
|
||||||
for i := displayPoints; i < displaySegments; i++ {
|
for i := displayPoints; i < displaySegments; i++ {
|
||||||
result.WriteString(unfilledChar)
|
result.WriteString(unfilledChar)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/boolean-maybe/tiki/config"
|
||||||
|
)
|
||||||
|
|
||||||
func TestGeneratePointsVisual(t *testing.T) {
|
func TestGeneratePointsVisual(t *testing.T) {
|
||||||
|
blue := config.NewColorHex("#508cff")
|
||||||
|
gray := config.NewColorHex("#5f6982")
|
||||||
const blueColor = "[#508cff]"
|
const blueColor = "[#508cff]"
|
||||||
const grayColor = "[#5f6982]"
|
const grayColor = "[#5f6982]"
|
||||||
const resetColor = "[-]"
|
const resetColor = "[-]"
|
||||||
|
|
@ -65,7 +71,7 @@ func TestGeneratePointsVisual(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := GeneratePointsVisual(tt.points, tt.maxPoints, blueColor, grayColor)
|
got := GeneratePointsVisual(tt.points, tt.maxPoints, blue, gray)
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("GeneratePointsVisual(%d, %d) = %q, want %q", tt.points, tt.maxPoints, got, tt.want)
|
t.Errorf("GeneratePointsVisual(%d, %d) = %q, want %q", tt.points, tt.maxPoints, got, tt.want)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func DrawSingleLineBorder(screen tcell.Screen, x, y, width, height int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
style := tcell.StyleDefault.Foreground(colors.TaskBoxUnselectedBorder).Background(colors.ContentBackgroundColor)
|
style := tcell.StyleDefault.Foreground(colors.TaskBoxUnselectedBorder.TCell()).Background(colors.ContentBackgroundColor.TCell())
|
||||||
|
|
||||||
DrawSingleLineBorderWithStyle(screen, x, y, width, height, style)
|
DrawSingleLineBorderWithStyle(screen, x, y, width, height, style)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ func NewDokiView(
|
||||||
|
|
||||||
func (dv *DokiView) build() {
|
func (dv *DokiView) build() {
|
||||||
// title bar with gradient background using plugin color
|
// title bar with gradient background using plugin color
|
||||||
textColor := tcell.ColorDefault
|
textColor := config.DefaultColor()
|
||||||
if dv.pluginDef.Foreground != tcell.ColorDefault {
|
if !dv.pluginDef.Foreground.IsDefault() {
|
||||||
textColor = dv.pluginDef.Foreground
|
textColor = dv.pluginDef.Foreground
|
||||||
}
|
}
|
||||||
dv.titleBar = NewGradientCaptionRow([]string{dv.pluginDef.Name}, nil, dv.pluginDef.Background, textColor)
|
dv.titleBar = NewGradientCaptionRow([]string{dv.pluginDef.Name}, nil, dv.pluginDef.Background, textColor)
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ type GradientCaptionRow struct {
|
||||||
laneNames []string
|
laneNames []string
|
||||||
laneWidths []int // proportional widths (same values used in tview.Flex)
|
laneWidths []int // proportional widths (same values used in tview.Flex)
|
||||||
gradient config.Gradient // computed gradient (for truecolor/256-color terminals)
|
gradient config.Gradient // computed gradient (for truecolor/256-color terminals)
|
||||||
textColor tcell.Color
|
textColor config.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGradientCaptionRow creates a new gradient caption row widget.
|
// NewGradientCaptionRow creates a new gradient caption row widget.
|
||||||
// laneWidths should match the flex proportions used for lane layout (nil = equal).
|
// laneWidths should match the flex proportions used for lane layout (nil = equal).
|
||||||
func NewGradientCaptionRow(laneNames []string, laneWidths []int, bgColor tcell.Color, textColor tcell.Color) *GradientCaptionRow {
|
func NewGradientCaptionRow(laneNames []string, laneWidths []int, bgColor config.Color, textColor config.Color) *GradientCaptionRow {
|
||||||
return &GradientCaptionRow{
|
return &GradientCaptionRow{
|
||||||
Box: tview.NewBox(),
|
Box: tview.NewBox(),
|
||||||
laneNames: laneNames,
|
laneNames: laneNames,
|
||||||
|
|
@ -106,7 +106,7 @@ func (gcr *GradientCaptionRow) Draw(screen tcell.Screen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the cell with gradient background
|
// Render the cell with gradient background
|
||||||
style := tcell.StyleDefault.Foreground(gcr.textColor).Background(bgColor)
|
style := tcell.StyleDefault.Foreground(gcr.textColor.TCell()).Background(bgColor)
|
||||||
for row := 0; row < height; row++ {
|
for row := 0; row < height; row++ {
|
||||||
screen.SetContent(x+col, y+row, char, nil, style)
|
screen.SetContent(x+col, y+row, char, nil, style)
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +154,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// computeCaptionGradient computes the gradient for caption background from a base color.
|
// computeCaptionGradient computes the gradient for caption background from a base color.
|
||||||
func computeCaptionGradient(primary tcell.Color) config.Gradient {
|
func computeCaptionGradient(primary config.Color) config.Gradient {
|
||||||
fallback := config.GetColors().CaptionFallbackGradient
|
fallback := config.GetColors().CaptionFallbackGradient
|
||||||
if useVibrantPluginGradient {
|
if useVibrantPluginGradient {
|
||||||
return gradient.GradientFromColorVibrant(primary, vibrantBoost, fallback)
|
return gradient.GradientFromColorVibrant(primary, vibrantBoost, fallback)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ type ChartWidget struct {
|
||||||
func NewChartWidgetSimple() *ChartWidget {
|
func NewChartWidgetSimple() *ChartWidget {
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
chartTheme := barchart.DefaultTheme()
|
chartTheme := barchart.DefaultTheme()
|
||||||
chartTheme.AxisColor = colors.BurndownChartAxisColor
|
chartTheme.AxisColor = colors.BurndownChartAxisColor.TCell()
|
||||||
chartTheme.BarGradientFrom = colors.BurndownHeaderGradientFrom.Start // Use header-specific gradient
|
chartTheme.BarGradientFrom = colors.BurndownHeaderGradientFrom.Start // Use header-specific gradient
|
||||||
chartTheme.BarGradientTo = colors.BurndownHeaderGradientTo.Start // Use header-specific gradient
|
chartTheme.BarGradientTo = colors.BurndownHeaderGradientTo.Start // Use header-specific gradient
|
||||||
chartTheme.DotChar = '⣿' // braille full cell for compact dots
|
chartTheme.DotChar = '⣿' // braille full cell for compact dots
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import "github.com/boolean-maybe/tiki/config"
|
||||||
|
|
||||||
// ColorScheme defines color pairs for different action categories
|
// ColorScheme defines color pairs for different action categories
|
||||||
type ColorScheme struct {
|
type ColorScheme struct {
|
||||||
KeyColor string
|
KeyColor config.Color
|
||||||
LabelColor string
|
LabelColor config.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
// getColorScheme returns the color scheme for the given action type.
|
// getColorScheme returns the color scheme for the given action type.
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ func buildGridRow(rowData []cellData, maxKeyLenPerCol, maxLabelLenPerCol []int,
|
||||||
|
|
||||||
// Render cell with colors
|
// Render cell with colors
|
||||||
scheme := getColorScheme(cell.colorType)
|
scheme := getColorScheme(cell.colorType)
|
||||||
fmt.Fprintf(&line, "[%s]<%s>[%s]", scheme.KeyColor, cell.key, scheme.LabelColor)
|
fmt.Fprintf(&line, "%s<%s>%s", scheme.KeyColor.Tag().String(), cell.key, scheme.LabelColor.Tag().String())
|
||||||
|
|
||||||
// Add key padding
|
// Add key padding
|
||||||
if keyPadding := maxKeyLenPerCol[col] - cell.keyLen; keyPadding > 0 {
|
if keyPadding := maxKeyLenPerCol[col] - cell.keyLen; keyPadding > 0 {
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,15 @@ func NewInfoWidget() *InfoWidget {
|
||||||
func (iw *InfoWidget) SetViewInfo(name, description string) {
|
func (iw *InfoWidget) SetViewInfo(name, description string) {
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
|
|
||||||
// convert "[orange]" to "[orange::b]" for bold name
|
boldColor := colors.HeaderInfoLabel.Tag().Bold().String()
|
||||||
boldColor := makeBold(colors.HeaderInfoLabel)
|
separatorTag := colors.HeaderInfoSeparator.Tag().String()
|
||||||
|
descTag := colors.HeaderInfoDesc.Tag().String()
|
||||||
|
|
||||||
separator := strings.Repeat("─", InfoWidth)
|
separator := strings.Repeat("─", InfoWidth)
|
||||||
|
|
||||||
var text string
|
var text string
|
||||||
if description != "" {
|
if description != "" {
|
||||||
text = fmt.Sprintf("%s%s[-::-]\n%s%s[-]\n%s%s", boldColor, name, colors.HeaderInfoSeparator, separator, colors.HeaderInfoDesc, description)
|
text = fmt.Sprintf("%s%s[-::-]\n%s%s[-]\n%s%s", boldColor, name, separatorTag, separator, descTag, description)
|
||||||
} else {
|
} else {
|
||||||
text = fmt.Sprintf("%s%s[-::-]", boldColor, name)
|
text = fmt.Sprintf("%s%s[-::-]", boldColor, name)
|
||||||
}
|
}
|
||||||
|
|
@ -45,11 +46,6 @@ func (iw *InfoWidget) SetViewInfo(name, description string) {
|
||||||
iw.SetText(text)
|
iw.SetText(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeBold converts a tview color tag like "[orange]" to "[orange::b]"
|
|
||||||
func makeBold(colorTag string) string {
|
|
||||||
return strings.TrimSuffix(colorTag, "]") + "::b]"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Primitive returns the underlying tview primitive
|
// Primitive returns the underlying tview primitive
|
||||||
func (iw *InfoWidget) Primitive() tview.Primitive {
|
func (iw *InfoWidget) Primitive() tview.Primitive {
|
||||||
return iw.TextView
|
return iw.TextView
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func NewNavigableMarkdown(cfg NavigableMarkdownConfig) *NavigableMarkdown {
|
||||||
renderer = renderer.WithCodeBorder(b)
|
renderer = renderer.WithCodeBorder(b)
|
||||||
}
|
}
|
||||||
nm.viewer.SetRenderer(renderer)
|
nm.viewer.SetRenderer(renderer)
|
||||||
nm.viewer.SetBackgroundColor(config.GetColors().ContentBackgroundColor)
|
nm.viewer.SetBackgroundColor(config.GetColors().ContentBackgroundColor.TCell())
|
||||||
if cfg.ImageManager != nil && cfg.ImageManager.Supported() {
|
if cfg.ImageManager != nil && cfg.ImageManager.Supported() {
|
||||||
nm.viewer.SetImageManager(cfg.ImageManager)
|
nm.viewer.SetImageManager(cfg.ImageManager)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ func NewSearchBox() *SearchBox {
|
||||||
|
|
||||||
// Configure the input field (border drawn manually in Draw)
|
// Configure the input field (border drawn manually in Draw)
|
||||||
inputField.SetLabel("> ")
|
inputField.SetLabel("> ")
|
||||||
inputField.SetLabelColor(colors.SearchBoxLabelColor)
|
inputField.SetLabelColor(colors.SearchBoxLabelColor.TCell())
|
||||||
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
inputField.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
inputField.SetFieldTextColor(colors.ContentTextColor)
|
inputField.SetFieldTextColor(colors.ContentTextColor.TCell())
|
||||||
inputField.SetBorder(false)
|
inputField.SetBorder(false)
|
||||||
|
|
||||||
sb := &SearchBox{
|
sb := &SearchBox{
|
||||||
|
|
@ -60,7 +60,7 @@ func (sb *SearchBox) Draw(screen tcell.Screen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill interior with theme-aware background color
|
// Fill interior with theme-aware background color
|
||||||
bgColor := config.GetColors().ContentBackgroundColor
|
bgColor := config.GetColors().ContentBackgroundColor.TCell()
|
||||||
bgStyle := tcell.StyleDefault.Background(bgColor)
|
bgStyle := tcell.StyleDefault.Background(bgColor)
|
||||||
for row := y; row < y+height; row++ {
|
for row := y; row < y+height; row++ {
|
||||||
for col := x; col < x+width; col++ {
|
for col := x; col < x+width; col++ {
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ func (sw *StatuslineWidget) render(width int) {
|
||||||
if padLen < 1 {
|
if padLen < 1 {
|
||||||
padLen = 1
|
padLen = 1
|
||||||
}
|
}
|
||||||
padding := fmt.Sprintf("[-:%s]%s[-:-]", colors.StatuslineFillBg, strings.Repeat(" ", padLen))
|
padding := fmt.Sprintf("[-:%s]%s[-:-]", colors.StatuslineFillBg.Hex(), strings.Repeat(" ", padLen))
|
||||||
|
|
||||||
sw.SetText(left + msgRendered + padding + rightStats)
|
sw.SetText(left + msgRendered + padding + rightStats)
|
||||||
}
|
}
|
||||||
|
|
@ -121,14 +121,14 @@ func (sw *StatuslineWidget) renderLeftSegments(segments []statSegment, colors *c
|
||||||
bg, fg := segmentColors(i, colors)
|
bg, fg := segmentColors(i, colors)
|
||||||
|
|
||||||
// segment text: " value "
|
// segment text: " value "
|
||||||
fmt.Fprintf(&b, "[%s:%s] %s ", fg, bg, seg.value)
|
fmt.Fprintf(&b, "[%s:%s] %s ", fg.Hex(), bg.Hex(), seg.value)
|
||||||
|
|
||||||
// separator: fg = current bg (creates the arrow), bg = next segment's bg or fill
|
// separator: fg = current bg (creates the arrow), bg = next segment's bg or fill
|
||||||
nextBg := colors.StatuslineFillBg
|
nextBg := colors.StatuslineFillBg
|
||||||
if i < len(segments)-1 {
|
if i < len(segments)-1 {
|
||||||
nextBg, _ = segmentColors(i+1, colors)
|
nextBg, _ = segmentColors(i+1, colors)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, "[%s:%s]%s", bg, nextBg, separatorRight)
|
fmt.Fprintf(&b, "[%s:%s]%s", bg.Hex(), nextBg.Hex(), separatorRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset colors
|
// reset colors
|
||||||
|
|
@ -153,10 +153,10 @@ func (sw *StatuslineWidget) renderRightSegments(segments []statSegment, colors *
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
prevBg, _ = segmentColors(i-1, colors)
|
prevBg, _ = segmentColors(i-1, colors)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, "[%s:%s]%s", bg, prevBg, separatorLeft)
|
fmt.Fprintf(&b, "[%s:%s]%s", bg.Hex(), prevBg.Hex(), separatorLeft)
|
||||||
|
|
||||||
// segment text
|
// segment text
|
||||||
fmt.Fprintf(&b, "[%s:%s] %s ", fg, bg, seg.value)
|
fmt.Fprintf(&b, "[%s:%s] %s ", fg.Hex(), bg.Hex(), seg.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset colors
|
// reset colors
|
||||||
|
|
@ -166,7 +166,7 @@ func (sw *StatuslineWidget) renderRightSegments(segments []statSegment, colors *
|
||||||
|
|
||||||
// segmentColors returns (bg, fg) for a segment at the given index.
|
// segmentColors returns (bg, fg) for a segment at the given index.
|
||||||
// Even indices use accent colors, odd indices use normal colors.
|
// Even indices use accent colors, odd indices use normal colors.
|
||||||
func segmentColors(index int, colors *config.ColorConfig) (string, string) {
|
func segmentColors(index int, colors *config.ColorConfig) (config.Color, config.Color) {
|
||||||
if index%2 == 0 {
|
if index%2 == 0 {
|
||||||
return colors.StatuslineAccentBg, colors.StatuslineAccentFg
|
return colors.StatuslineAccentBg, colors.StatuslineAccentFg
|
||||||
}
|
}
|
||||||
|
|
@ -179,11 +179,11 @@ func (sw *StatuslineWidget) renderMessage(msg string, level model.MessageLevel,
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
fg, bg := messageColors(level, colors)
|
fg, bg := messageColors(level, colors)
|
||||||
return fmt.Sprintf("[%s:%s] %s [-:-]", fg, bg, msg)
|
return fmt.Sprintf("[%s:%s] %s [-:-]", fg.Hex(), bg.Hex(), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// messageColors returns (fg, bg) for the given message level
|
// messageColors returns (fg, bg) for the given message level
|
||||||
func messageColors(level model.MessageLevel, colors *config.ColorConfig) (string, string) {
|
func messageColors(level model.MessageLevel, colors *config.ColorConfig) (config.Color, config.Color) {
|
||||||
switch level {
|
switch level {
|
||||||
case model.MessageLevelError:
|
case model.MessageLevelError:
|
||||||
return colors.StatuslineErrorFg, colors.StatuslineErrorBg
|
return colors.StatuslineErrorFg, colors.StatuslineErrorBg
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@ import (
|
||||||
|
|
||||||
func testColors() *config.ColorConfig {
|
func testColors() *config.ColorConfig {
|
||||||
return &config.ColorConfig{
|
return &config.ColorConfig{
|
||||||
StatuslineBg: "#normal_bg",
|
StatuslineBg: config.NewColorHex("#111111"),
|
||||||
StatuslineFg: "#normal_fg",
|
StatuslineFg: config.NewColorHex("#222222"),
|
||||||
StatuslineAccentBg: "#accent_bg",
|
StatuslineAccentBg: config.NewColorHex("#333333"),
|
||||||
StatuslineAccentFg: "#accent_fg",
|
StatuslineAccentFg: config.NewColorHex("#444444"),
|
||||||
StatuslineInfoFg: "#info_fg",
|
StatuslineInfoFg: config.NewColorHex("#555555"),
|
||||||
StatuslineInfoBg: "#info_bg",
|
StatuslineInfoBg: config.NewColorHex("#666666"),
|
||||||
StatuslineErrorFg: "#error_fg",
|
StatuslineErrorFg: config.NewColorHex("#777777"),
|
||||||
StatuslineErrorBg: "#error_bg",
|
StatuslineErrorBg: config.NewColorHex("#888888"),
|
||||||
StatuslineFillBg: "#fill_bg",
|
StatuslineFillBg: config.NewColorHex("#999999"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,11 +108,11 @@ func TestRenderLeftSegments_singleSegment(t *testing.T) {
|
||||||
result := sw.renderLeftSegments(segments, colors)
|
result := sw.renderLeftSegments(segments, colors)
|
||||||
|
|
||||||
// first segment (index 0) uses accent colors
|
// first segment (index 0) uses accent colors
|
||||||
if !strings.Contains(result, "[#accent_fg:#accent_bg] v1.0 ") {
|
if !strings.Contains(result, "[#444444:#333333] v1.0 ") {
|
||||||
t.Errorf("first segment should use accent colors, got %q", result)
|
t.Errorf("first segment should use accent colors, got %q", result)
|
||||||
}
|
}
|
||||||
// separator: fg=accent_bg (current), bg=fill (last segment)
|
// separator: fg=accent_bg (current), bg=fill (last segment)
|
||||||
if !strings.Contains(result, "[#accent_bg:#fill_bg]"+separatorRight) {
|
if !strings.Contains(result, "[#333333:#999999]"+separatorRight) {
|
||||||
t.Errorf("separator should transition to fill background, got %q", result)
|
t.Errorf("separator should transition to fill background, got %q", result)
|
||||||
}
|
}
|
||||||
// ends with color reset
|
// ends with color reset
|
||||||
|
|
@ -132,15 +132,15 @@ func TestRenderLeftSegments_twoSegments(t *testing.T) {
|
||||||
result := sw.renderLeftSegments(segments, colors)
|
result := sw.renderLeftSegments(segments, colors)
|
||||||
|
|
||||||
// first segment (index 0): accent
|
// first segment (index 0): accent
|
||||||
if !strings.Contains(result, "[#accent_fg:#accent_bg] v1.0 ") {
|
if !strings.Contains(result, "[#444444:#333333] v1.0 ") {
|
||||||
t.Errorf("first segment should use accent, got %q", result)
|
t.Errorf("first segment should use accent, got %q", result)
|
||||||
}
|
}
|
||||||
// separator between 1st and 2nd: fg=accent_bg, bg=normal_bg
|
// separator between 1st and 2nd: fg=accent_bg, bg=normal_bg
|
||||||
if !strings.Contains(result, "[#accent_bg:#normal_bg]"+separatorRight) {
|
if !strings.Contains(result, "[#333333:#111111]"+separatorRight) {
|
||||||
t.Errorf("separator should transition accent→normal, got %q", result)
|
t.Errorf("separator should transition accent→normal, got %q", result)
|
||||||
}
|
}
|
||||||
// second segment (index 1): normal
|
// second segment (index 1): normal
|
||||||
if !strings.Contains(result, "[#normal_fg:#normal_bg] main ") {
|
if !strings.Contains(result, "[#222222:#111111] main ") {
|
||||||
t.Errorf("second segment should use normal colors, got %q", result)
|
t.Errorf("second segment should use normal colors, got %q", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,11 +161,11 @@ func TestRenderRightSegments_singleSegment(t *testing.T) {
|
||||||
result := sw.renderRightSegments(segments, colors)
|
result := sw.renderRightSegments(segments, colors)
|
||||||
|
|
||||||
// index 0 (even) → accent colors
|
// index 0 (even) → accent colors
|
||||||
if !strings.Contains(result, "[#accent_fg:#accent_bg] 42 ") {
|
if !strings.Contains(result, "[#444444:#333333] 42 ") {
|
||||||
t.Errorf("segment 0 should use accent colors, got %q", result)
|
t.Errorf("segment 0 should use accent colors, got %q", result)
|
||||||
}
|
}
|
||||||
// separator: fg=accent_bg, bg=fill (first right segment)
|
// separator: fg=accent_bg, bg=fill (first right segment)
|
||||||
if !strings.Contains(result, "[#accent_bg:#fill_bg]"+separatorLeft) {
|
if !strings.Contains(result, "[#333333:#999999]"+separatorLeft) {
|
||||||
t.Errorf("separator should be accent→fill, got %q", result)
|
t.Errorf("separator should be accent→fill, got %q", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,15 +181,15 @@ func TestRenderRightSegments_twoSegments(t *testing.T) {
|
||||||
result := sw.renderRightSegments(segments, colors)
|
result := sw.renderRightSegments(segments, colors)
|
||||||
|
|
||||||
// index 0 (even) → accent
|
// index 0 (even) → accent
|
||||||
if !strings.Contains(result, "[#accent_fg:#accent_bg] 42 ") {
|
if !strings.Contains(result, "[#444444:#333333] 42 ") {
|
||||||
t.Errorf("segment 0 should use accent, got %q", result)
|
t.Errorf("segment 0 should use accent, got %q", result)
|
||||||
}
|
}
|
||||||
// index 1 (odd) → normal
|
// index 1 (odd) → normal
|
||||||
if !strings.Contains(result, "[#normal_fg:#normal_bg] 10 ") {
|
if !strings.Contains(result, "[#222222:#111111] 10 ") {
|
||||||
t.Errorf("segment 1 should use normal, got %q", result)
|
t.Errorf("segment 1 should use normal, got %q", result)
|
||||||
}
|
}
|
||||||
// separator between 0→1: fg=normal_bg, bg=accent_bg (prev segment)
|
// separator between 0→1: fg=normal_bg, bg=accent_bg (prev segment)
|
||||||
if !strings.Contains(result, "[#normal_bg:#accent_bg]"+separatorLeft) {
|
if !strings.Contains(result, "[#111111:#333333]"+separatorLeft) {
|
||||||
t.Errorf("separator between segments should show normal→accent transition, got %q", result)
|
t.Errorf("separator between segments should show normal→accent transition, got %q", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,13 +206,13 @@ func TestRenderRightSegments_threeSegments(t *testing.T) {
|
||||||
result := sw.renderRightSegments(segments, colors)
|
result := sw.renderRightSegments(segments, colors)
|
||||||
|
|
||||||
// index 0 → accent, index 1 → normal, index 2 → accent
|
// index 0 → accent, index 1 → normal, index 2 → accent
|
||||||
if !strings.Contains(result, "[#accent_fg:#accent_bg] a ") {
|
if !strings.Contains(result, "[#444444:#333333] a ") {
|
||||||
t.Error("segment 0 should use accent")
|
t.Error("segment 0 should use accent")
|
||||||
}
|
}
|
||||||
if !strings.Contains(result, "[#normal_fg:#normal_bg] b ") {
|
if !strings.Contains(result, "[#222222:#111111] b ") {
|
||||||
t.Error("segment 1 should use normal")
|
t.Error("segment 1 should use normal")
|
||||||
}
|
}
|
||||||
if !strings.Contains(result, "[#accent_fg:#accent_bg] c ") {
|
if !strings.Contains(result, "[#444444:#333333] c ") {
|
||||||
t.Error("segment 2 should use accent")
|
t.Error("segment 2 should use accent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +230,7 @@ func TestRenderMessage_info(t *testing.T) {
|
||||||
colors := testColors()
|
colors := testColors()
|
||||||
result := sw.renderMessage("task saved", model.MessageLevelInfo, colors)
|
result := sw.renderMessage("task saved", model.MessageLevelInfo, colors)
|
||||||
|
|
||||||
if !strings.Contains(result, "[#info_fg:#info_bg] task saved ") {
|
if !strings.Contains(result, "[#555555:#666666] task saved ") {
|
||||||
t.Errorf("info message should use info colors, got %q", result)
|
t.Errorf("info message should use info colors, got %q", result)
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(result, "[-:-]") {
|
if !strings.HasSuffix(result, "[-:-]") {
|
||||||
|
|
@ -243,7 +243,7 @@ func TestRenderMessage_error(t *testing.T) {
|
||||||
colors := testColors()
|
colors := testColors()
|
||||||
result := sw.renderMessage("validation failed", model.MessageLevelError, colors)
|
result := sw.renderMessage("validation failed", model.MessageLevelError, colors)
|
||||||
|
|
||||||
if !strings.Contains(result, "[#error_fg:#error_bg] validation failed ") {
|
if !strings.Contains(result, "[#777777:#888888] validation failed ") {
|
||||||
t.Errorf("error message should use error colors, got %q", result)
|
t.Errorf("error message should use error colors, got %q", result)
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(result, "[-:-]") {
|
if !strings.HasSuffix(result, "[-:-]") {
|
||||||
|
|
@ -255,18 +255,18 @@ func TestRightSegmentColors(t *testing.T) {
|
||||||
colors := testColors()
|
colors := testColors()
|
||||||
|
|
||||||
bg0, fg0 := segmentColors(0, colors)
|
bg0, fg0 := segmentColors(0, colors)
|
||||||
if bg0 != "#accent_bg" || fg0 != "#accent_fg" {
|
if bg0.Hex() != colors.StatuslineAccentBg.Hex() || fg0.Hex() != colors.StatuslineAccentFg.Hex() {
|
||||||
t.Errorf("index 0: got (%s, %s), want accent", bg0, fg0)
|
t.Errorf("index 0: got (%s, %s), want accent", bg0.Hex(), fg0.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
bg1, fg1 := segmentColors(1, colors)
|
bg1, fg1 := segmentColors(1, colors)
|
||||||
if bg1 != "#normal_bg" || fg1 != "#normal_fg" {
|
if bg1.Hex() != colors.StatuslineBg.Hex() || fg1.Hex() != colors.StatuslineFg.Hex() {
|
||||||
t.Errorf("index 1: got (%s, %s), want normal", bg1, fg1)
|
t.Errorf("index 1: got (%s, %s), want normal", bg1.Hex(), fg1.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
bg2, fg2 := segmentColors(2, colors)
|
bg2, fg2 := segmentColors(2, colors)
|
||||||
if bg2 != "#accent_bg" || fg2 != "#accent_fg" {
|
if bg2.Hex() != colors.StatuslineAccentBg.Hex() || fg2.Hex() != colors.StatuslineAccentFg.Hex() {
|
||||||
t.Errorf("index 2: got (%s, %s), want accent", bg2, fg2)
|
t.Errorf("index 2: got (%s, %s), want accent", bg2.Hex(), fg2.Hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ import (
|
||||||
// applyFrameStyle applies selected/unselected styling to a frame
|
// applyFrameStyle applies selected/unselected styling to a frame
|
||||||
func applyFrameStyle(frame *tview.Frame, selected bool, colors *config.ColorConfig) {
|
func applyFrameStyle(frame *tview.Frame, selected bool, colors *config.ColorConfig) {
|
||||||
if selected {
|
if selected {
|
||||||
frame.SetBorderColor(colors.TaskBoxSelectedBorder)
|
frame.SetBorderColor(colors.TaskBoxSelectedBorder.TCell())
|
||||||
} else {
|
} else {
|
||||||
frame.SetBorderColor(colors.TaskBoxUnselectedBorder)
|
frame.SetBorderColor(colors.TaskBoxUnselectedBorder.TCell())
|
||||||
if colors.TaskBoxUnselectedBackground != tcell.ColorDefault {
|
if !colors.TaskBoxUnselectedBackground.IsDefault() {
|
||||||
frame.SetBackgroundColor(colors.TaskBoxUnselectedBackground)
|
frame.SetBackgroundColor(colors.TaskBoxUnselectedBackground.TCell())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,11 +35,14 @@ func buildCompactTaskContent(task *taskpkg.Task, colors *config.ColorConfig, ava
|
||||||
priorityEmoji := taskpkg.PriorityLabel(task.Priority)
|
priorityEmoji := taskpkg.PriorityLabel(task.Priority)
|
||||||
pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor)
|
pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor)
|
||||||
|
|
||||||
|
titleTag := colors.TaskBoxTitleColor.Tag().String()
|
||||||
|
labelTag := colors.TaskBoxLabelColor.Tag().String()
|
||||||
|
|
||||||
return fmt.Sprintf("%s %s\n%s%s[-]\n%spriority[-] %s %spoints[-] %s%s[-]",
|
return fmt.Sprintf("%s %s\n%s%s[-]\n%spriority[-] %s %spoints[-] %s%s[-]",
|
||||||
emoji, idGradient,
|
emoji, idGradient,
|
||||||
colors.TaskBoxTitleColor, truncatedTitle,
|
titleTag, truncatedTitle,
|
||||||
colors.TaskBoxLabelColor, priorityEmoji,
|
labelTag, priorityEmoji,
|
||||||
colors.TaskBoxLabelColor, colors.TaskBoxLabelColor, pointsVisual)
|
labelTag, labelTag, pointsVisual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildExpandedTaskContent builds the content string for expanded task display
|
// buildExpandedTaskContent builds the content string for expanded task display
|
||||||
|
|
@ -64,25 +67,30 @@ func buildExpandedTaskContent(task *taskpkg.Task, colors *config.ColorConfig, av
|
||||||
descLine3 = tview.Escape(util.TruncateText(descLines[2], availableWidth))
|
descLine3 = tview.Escape(util.TruncateText(descLines[2], availableWidth))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
titleTag := colors.TaskBoxTitleColor.Tag().String()
|
||||||
|
labelTag := colors.TaskBoxLabelColor.Tag().String()
|
||||||
|
descTag := colors.TaskBoxDescriptionColor.Tag().String()
|
||||||
|
tagValueTag := colors.TaskBoxTagValueColor.Tag().String()
|
||||||
|
|
||||||
// Build tags string
|
// Build tags string
|
||||||
tagsStr := ""
|
tagsStr := ""
|
||||||
if len(task.Tags) > 0 {
|
if len(task.Tags) > 0 {
|
||||||
tagsStr = colors.TaskBoxLabelColor + "Tags:[-] " + colors.TaskBoxTagValueColor + tview.Escape(util.TruncateText(strings.Join(task.Tags, ", "), availableWidth-6)) + "[-]"
|
tagsStr = labelTag + "Tags:[-] " + tagValueTag + tview.Escape(util.TruncateText(strings.Join(task.Tags, ", "), availableWidth-6)) + "[-]"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build priority/points line
|
// Build priority/points line
|
||||||
priorityEmoji := taskpkg.PriorityLabel(task.Priority)
|
priorityEmoji := taskpkg.PriorityLabel(task.Priority)
|
||||||
pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor)
|
pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor)
|
||||||
priorityPointsStr := fmt.Sprintf("%spriority[-] %s %spoints[-] %s%s[-]",
|
priorityPointsStr := fmt.Sprintf("%spriority[-] %s %spoints[-] %s%s[-]",
|
||||||
colors.TaskBoxLabelColor, priorityEmoji,
|
labelTag, priorityEmoji,
|
||||||
colors.TaskBoxLabelColor, colors.TaskBoxLabelColor, pointsVisual)
|
labelTag, labelTag, pointsVisual)
|
||||||
|
|
||||||
return fmt.Sprintf("%s %s\n%s%s[-]\n%s%s[-]\n%s%s[-]\n%s%s[-]\n%s\n%s",
|
return fmt.Sprintf("%s %s\n%s%s[-]\n%s%s[-]\n%s%s[-]\n%s%s[-]\n%s\n%s",
|
||||||
emoji, idGradient,
|
emoji, idGradient,
|
||||||
colors.TaskBoxTitleColor, truncatedTitle,
|
titleTag, truncatedTitle,
|
||||||
colors.TaskBoxDescriptionColor, descLine1,
|
descTag, descLine1,
|
||||||
colors.TaskBoxDescriptionColor, descLine2,
|
descTag, descLine2,
|
||||||
colors.TaskBoxDescriptionColor, descLine3,
|
descTag, descLine3,
|
||||||
tagsStr, priorityPointsStr)
|
tagsStr, priorityPointsStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ func (b *Base) assembleMetadataBox(
|
||||||
metadataBox := tview.NewFrame(metadataContainer).SetBorders(0, 0, 0, 0, 0, 0)
|
metadataBox := tview.NewFrame(metadataContainer).SetBorders(0, 0, 0, 0, 0, 0)
|
||||||
metadataBox.SetBorder(true).SetTitle(
|
metadataBox.SetBorder(true).SetTitle(
|
||||||
fmt.Sprintf(" %s ", gradient.RenderAdaptiveGradientText(task.ID, colors.TaskDetailIDColor, colors.FallbackTaskIDColor)),
|
fmt.Sprintf(" %s ", gradient.RenderAdaptiveGradientText(task.ID, colors.TaskDetailIDColor, colors.FallbackTaskIDColor)),
|
||||||
).SetBorderColor(colors.TaskBoxUnselectedBorder)
|
).SetBorderColor(colors.TaskBoxUnselectedBorder.TCell())
|
||||||
metadataBox.SetBorderPadding(1, 0, 2, 2)
|
metadataBox.SetBorderPadding(1, 0, 2, 2)
|
||||||
|
|
||||||
return metadataBox
|
return metadataBox
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ type FieldRenderContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDimOrFullColor returns dim color if in edit mode and not focused, otherwise full color
|
// getDimOrFullColor returns dim color if in edit mode and not focused, otherwise full color
|
||||||
func getDimOrFullColor(mode RenderMode, focused bool, fullColor string, dimColor string) string {
|
func getDimOrFullColor(mode RenderMode, focused bool, fullColor config.Color, dimColor config.Color) config.Color {
|
||||||
if mode == RenderModeEdit && !focused {
|
if mode == RenderModeEdit && !focused {
|
||||||
return dimColor
|
return dimColor
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ func getDimOrFullColor(mode RenderMode, focused bool, fullColor string, dimColor
|
||||||
|
|
||||||
// getFocusMarker returns the focus marker string (arrow + text color) from colors config
|
// getFocusMarker returns the focus marker string (arrow + text color) from colors config
|
||||||
func getFocusMarker(colors *config.ColorConfig) string {
|
func getFocusMarker(colors *config.ColorConfig) string {
|
||||||
return colors.TaskDetailEditFocusMarker + "► " + colors.TaskDetailEditFocusText
|
return colors.TaskDetailEditFocusMarker.Tag().String() + "► " + colors.TaskDetailEditFocusText.Tag().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderStatusText renders a status field as read-only text
|
// RenderStatusText renders a status field as read-only text
|
||||||
|
|
@ -46,15 +46,15 @@ func RenderStatusText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitiv
|
||||||
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldStatus
|
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldStatus
|
||||||
statusDisplay := taskpkg.StatusDisplay(task.Status)
|
statusDisplay := taskpkg.StatusDisplay(task.Status)
|
||||||
|
|
||||||
labelColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor)
|
labelTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor).Tag().String()
|
||||||
valueColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor)
|
valueTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor).Tag().String()
|
||||||
|
|
||||||
focusMarker := ""
|
focusMarker := ""
|
||||||
if focused && ctx.Mode == RenderModeEdit {
|
if focused && ctx.Mode == RenderModeEdit {
|
||||||
focusMarker = getFocusMarker(ctx.Colors)
|
focusMarker = getFocusMarker(ctx.Colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelColor, "Status:", valueColor, statusDisplay)
|
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelTag, "Status:", valueTag, statusDisplay)
|
||||||
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
textView.SetBorderPadding(0, 0, 0, 0)
|
textView.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
@ -69,15 +69,15 @@ func RenderTypeText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive
|
||||||
typeDisplay = "[gray](none)[-]"
|
typeDisplay = "[gray](none)[-]"
|
||||||
}
|
}
|
||||||
|
|
||||||
labelColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor)
|
labelTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor).Tag().String()
|
||||||
valueColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor)
|
valueTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor).Tag().String()
|
||||||
|
|
||||||
focusMarker := ""
|
focusMarker := ""
|
||||||
if focused && ctx.Mode == RenderModeEdit {
|
if focused && ctx.Mode == RenderModeEdit {
|
||||||
focusMarker = getFocusMarker(ctx.Colors)
|
focusMarker = getFocusMarker(ctx.Colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelColor, "Type:", valueColor, typeDisplay)
|
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelTag, "Type:", valueTag, typeDisplay)
|
||||||
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
textView.SetBorderPadding(0, 0, 0, 0)
|
textView.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
@ -88,15 +88,15 @@ func RenderTypeText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive
|
||||||
func RenderPriorityText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
func RenderPriorityText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
||||||
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldPriority
|
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldPriority
|
||||||
|
|
||||||
labelColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor)
|
labelTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor).Tag().String()
|
||||||
valueColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor)
|
valueTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor).Tag().String()
|
||||||
|
|
||||||
focusMarker := ""
|
focusMarker := ""
|
||||||
if focused && ctx.Mode == RenderModeEdit {
|
if focused && ctx.Mode == RenderModeEdit {
|
||||||
focusMarker = getFocusMarker(ctx.Colors)
|
focusMarker = getFocusMarker(ctx.Colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelColor, "Priority:", valueColor, taskpkg.PriorityDisplay(task.Priority))
|
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelTag, "Priority:", valueTag, taskpkg.PriorityDisplay(task.Priority))
|
||||||
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
textView.SetBorderPadding(0, 0, 0, 0)
|
textView.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
@ -107,15 +107,15 @@ func RenderPriorityText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primit
|
||||||
func RenderAssigneeText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
func RenderAssigneeText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
||||||
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldAssignee
|
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldAssignee
|
||||||
|
|
||||||
labelColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor)
|
labelTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor).Tag().String()
|
||||||
valueColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor)
|
valueTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor).Tag().String()
|
||||||
|
|
||||||
focusMarker := ""
|
focusMarker := ""
|
||||||
if focused && ctx.Mode == RenderModeEdit {
|
if focused && ctx.Mode == RenderModeEdit {
|
||||||
focusMarker = getFocusMarker(ctx.Colors)
|
focusMarker = getFocusMarker(ctx.Colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelColor, "Assignee:", valueColor, tview.Escape(defaultString(task.Assignee, "Unassigned")))
|
text := fmt.Sprintf("%s%s%-10s%s%s", focusMarker, labelTag, "Assignee:", valueTag, tview.Escape(defaultString(task.Assignee, "Unassigned")))
|
||||||
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
textView.SetBorderPadding(0, 0, 0, 0)
|
textView.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
@ -126,15 +126,15 @@ func RenderAssigneeText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primit
|
||||||
func RenderPointsText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
func RenderPointsText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
||||||
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldPoints
|
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldPoints
|
||||||
|
|
||||||
labelColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor)
|
labelTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailLabelText, ctx.Colors.TaskDetailEditDimLabelColor).Tag().String()
|
||||||
valueColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor)
|
valueTag := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailValueText, ctx.Colors.TaskDetailEditDimValueColor).Tag().String()
|
||||||
|
|
||||||
focusMarker := ""
|
focusMarker := ""
|
||||||
if focused && ctx.Mode == RenderModeEdit {
|
if focused && ctx.Mode == RenderModeEdit {
|
||||||
focusMarker = getFocusMarker(ctx.Colors)
|
focusMarker = getFocusMarker(ctx.Colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf("%s%s%-10s%s%d", focusMarker, labelColor, "Points:", valueColor, task.Points)
|
text := fmt.Sprintf("%s%s%-10s%s%d", focusMarker, labelTag, "Points:", valueTag, task.Points)
|
||||||
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
textView := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
textView.SetBorderPadding(0, 0, 0, 0)
|
textView.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
@ -144,8 +144,14 @@ func RenderPointsText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitiv
|
||||||
// RenderTitleText renders a title as read-only text
|
// RenderTitleText renders a title as read-only text
|
||||||
func RenderTitleText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
func RenderTitleText(task *taskpkg.Task, ctx FieldRenderContext) tview.Primitive {
|
||||||
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldTitle
|
focused := ctx.Mode == RenderModeEdit && ctx.FocusedField == model.EditFieldTitle
|
||||||
titleColor := getDimOrFullColor(ctx.Mode, focused, ctx.Colors.TaskDetailTitleText[:len(ctx.Colors.TaskDetailTitleText)-1]+"::b]", ctx.Colors.TaskDetailEditDimTextColor)
|
var titleTag string
|
||||||
titleText := fmt.Sprintf("%s%s%s", titleColor, tview.Escape(task.Title), ctx.Colors.TaskDetailValueText)
|
if ctx.Mode == RenderModeEdit && !focused {
|
||||||
|
titleTag = ctx.Colors.TaskDetailEditDimTextColor.Tag().String()
|
||||||
|
} else {
|
||||||
|
titleTag = ctx.Colors.TaskDetailTitleText.Tag().Bold().String()
|
||||||
|
}
|
||||||
|
valueTag := ctx.Colors.TaskDetailValueText.Tag().String()
|
||||||
|
titleText := fmt.Sprintf("%s%s%s", titleTag, tview.Escape(task.Title), valueTag)
|
||||||
titleBox := tview.NewTextView().
|
titleBox := tview.NewTextView().
|
||||||
SetDynamicColors(true).
|
SetDynamicColors(true).
|
||||||
SetText(titleText)
|
SetText(titleText)
|
||||||
|
|
@ -159,7 +165,7 @@ func RenderTagsColumn(task *taskpkg.Task) tview.Primitive {
|
||||||
return tview.NewBox()
|
return tview.NewBox()
|
||||||
}
|
}
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
label := tview.NewTextView().SetDynamicColors(true).SetText(fmt.Sprintf("%sTags", colors.TaskDetailLabelText))
|
label := tview.NewTextView().SetDynamicColors(true).SetText(fmt.Sprintf("%sTags", colors.TaskDetailLabelText.Tag().String()))
|
||||||
label.SetBorderPadding(0, 0, 0, 0)
|
label.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
col := tview.NewFlex().SetDirection(tview.FlexRow)
|
col := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||||
|
|
@ -186,7 +192,7 @@ func RenderDependsOnColumn(task *taskpkg.Task, taskStore store.Store) tview.Prim
|
||||||
}
|
}
|
||||||
|
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
label := tview.NewTextView().SetDynamicColors(true).SetText(fmt.Sprintf("%sDepends On", colors.TaskDetailLabelText))
|
label := tview.NewTextView().SetDynamicColors(true).SetText(fmt.Sprintf("%sDepends On", colors.TaskDetailLabelText.Tag().String()))
|
||||||
label.SetBorderPadding(0, 0, 0, 0)
|
label.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
col := tview.NewFlex().SetDirection(tview.FlexRow)
|
col := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||||
|
|
@ -204,7 +210,7 @@ func RenderBlocksColumn(blocked []*taskpkg.Task) tview.Primitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
label := tview.NewTextView().SetDynamicColors(true).SetText(fmt.Sprintf("%sBlocks", colors.TaskDetailLabelText))
|
label := tview.NewTextView().SetDynamicColors(true).SetText(fmt.Sprintf("%sBlocks", colors.TaskDetailLabelText.Tag().String()))
|
||||||
label.SetBorderPadding(0, 0, 0, 0)
|
label.SetBorderPadding(0, 0, 0, 0)
|
||||||
|
|
||||||
col := tview.NewFlex().SetDirection(tview.FlexRow)
|
col := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||||
|
|
@ -217,7 +223,7 @@ func RenderBlocksColumn(blocked []*taskpkg.Task) tview.Primitive {
|
||||||
// RenderAuthorText renders the author field as read-only text
|
// RenderAuthorText renders the author field as read-only text
|
||||||
func RenderAuthorText(task *taskpkg.Task, colors *config.ColorConfig) tview.Primitive {
|
func RenderAuthorText(task *taskpkg.Task, colors *config.ColorConfig) tview.Primitive {
|
||||||
text := fmt.Sprintf("%s%-10s%s%s",
|
text := fmt.Sprintf("%s%-10s%s%s",
|
||||||
colors.TaskDetailEditDimLabelColor, "Author:", colors.TaskDetailValueText, tview.Escape(defaultString(task.CreatedBy, "Unknown")))
|
colors.TaskDetailEditDimLabelColor.Tag().String(), "Author:", colors.TaskDetailValueText.Tag().String(), tview.Escape(defaultString(task.CreatedBy, "Unknown")))
|
||||||
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
view.SetBorderPadding(0, 0, 0, 0)
|
view.SetBorderPadding(0, 0, 0, 0)
|
||||||
return view
|
return view
|
||||||
|
|
@ -230,7 +236,7 @@ func RenderCreatedText(task *taskpkg.Task, colors *config.ColorConfig) tview.Pri
|
||||||
createdAtStr = task.CreatedAt.Format("2006-01-02 15:04")
|
createdAtStr = task.CreatedAt.Format("2006-01-02 15:04")
|
||||||
}
|
}
|
||||||
text := fmt.Sprintf("%s%-10s%s%s",
|
text := fmt.Sprintf("%s%-10s%s%s",
|
||||||
colors.TaskDetailEditDimLabelColor, "Created:", colors.TaskDetailValueText, createdAtStr)
|
colors.TaskDetailEditDimLabelColor.Tag().String(), "Created:", colors.TaskDetailValueText.Tag().String(), createdAtStr)
|
||||||
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
view.SetBorderPadding(0, 0, 0, 0)
|
view.SetBorderPadding(0, 0, 0, 0)
|
||||||
return view
|
return view
|
||||||
|
|
@ -243,7 +249,7 @@ func RenderUpdatedText(task *taskpkg.Task, colors *config.ColorConfig) tview.Pri
|
||||||
updatedAtStr = task.UpdatedAt.Format("2006-01-02 15:04")
|
updatedAtStr = task.UpdatedAt.Format("2006-01-02 15:04")
|
||||||
}
|
}
|
||||||
text := fmt.Sprintf("%s%-10s%s%s",
|
text := fmt.Sprintf("%s%-10s%s%s",
|
||||||
colors.TaskDetailEditDimLabelColor, "Updated:", colors.TaskDetailValueText, updatedAtStr)
|
colors.TaskDetailEditDimLabelColor.Tag().String(), "Updated:", colors.TaskDetailValueText.Tag().String(), updatedAtStr)
|
||||||
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
view.SetBorderPadding(0, 0, 0, 0)
|
view.SetBorderPadding(0, 0, 0, 0)
|
||||||
return view
|
return view
|
||||||
|
|
@ -331,7 +337,7 @@ func RenderDueText(task *taskpkg.Task, colors *config.ColorConfig) tview.Primiti
|
||||||
dueDisplay = task.Due.Format("2006-01-02")
|
dueDisplay = task.Due.Format("2006-01-02")
|
||||||
}
|
}
|
||||||
text := fmt.Sprintf("%s%-12s%s%s",
|
text := fmt.Sprintf("%s%-12s%s%s",
|
||||||
colors.TaskDetailEditDimLabelColor, "Due:", colors.TaskDetailValueText, dueDisplay)
|
colors.TaskDetailEditDimLabelColor.Tag().String(), "Due:", colors.TaskDetailValueText.Tag().String(), dueDisplay)
|
||||||
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
view.SetBorderPadding(0, 0, 0, 0)
|
view.SetBorderPadding(0, 0, 0, 0)
|
||||||
return view
|
return view
|
||||||
|
|
@ -341,7 +347,7 @@ func RenderDueText(task *taskpkg.Task, colors *config.ColorConfig) tview.Primiti
|
||||||
func RenderRecurrenceText(task *taskpkg.Task, colors *config.ColorConfig) tview.Primitive {
|
func RenderRecurrenceText(task *taskpkg.Task, colors *config.ColorConfig) tview.Primitive {
|
||||||
display := taskpkg.RecurrenceDisplay(task.Recurrence)
|
display := taskpkg.RecurrenceDisplay(task.Recurrence)
|
||||||
text := fmt.Sprintf("%s%-12s%s%s",
|
text := fmt.Sprintf("%s%-12s%s%s",
|
||||||
colors.TaskDetailEditDimLabelColor, "Recurrence:", colors.TaskDetailValueText, display)
|
colors.TaskDetailEditDimLabelColor.Tag().String(), "Recurrence:", colors.TaskDetailValueText.Tag().String(), display)
|
||||||
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
view := tview.NewTextView().SetDynamicColors(true).SetText(text)
|
||||||
view.SetBorderPadding(0, 0, 0, 0)
|
view.SetBorderPadding(0, 0, 0, 0)
|
||||||
return view
|
return view
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,7 @@ func (ev *TaskEditView) ensureTagsTextArea(task *taskpkg.Task) *tview.TextArea {
|
||||||
ev.tagsTextArea.SetBorder(false)
|
ev.tagsTextArea.SetBorder(false)
|
||||||
ev.tagsTextArea.SetBorderPadding(1, 1, 2, 2)
|
ev.tagsTextArea.SetBorderPadding(1, 1, 2, 2)
|
||||||
ev.tagsTextArea.SetPlaceholder("Enter tags separated by spaces")
|
ev.tagsTextArea.SetPlaceholder("Enter tags separated by spaces")
|
||||||
ev.tagsTextArea.SetPlaceholderStyle(tcell.StyleDefault.Foreground(config.GetColors().TaskDetailPlaceholderColor))
|
ev.tagsTextArea.SetPlaceholderStyle(tcell.StyleDefault.Foreground(config.GetColors().TaskDetailPlaceholderColor.TCell()))
|
||||||
|
|
||||||
ev.tagsTextArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
ev.tagsTextArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
if event.Key() == tcell.KeyCtrlS {
|
if event.Key() == tcell.KeyCtrlS {
|
||||||
|
|
@ -448,8 +448,8 @@ func (ev *TaskEditView) ensureTitleInput(task *taskpkg.Task) *tview.InputField {
|
||||||
if ev.titleInput == nil {
|
if ev.titleInput == nil {
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
ev.titleInput = tview.NewInputField()
|
ev.titleInput = tview.NewInputField()
|
||||||
ev.titleInput.SetFieldBackgroundColor(colors.ContentBackgroundColor)
|
ev.titleInput.SetFieldBackgroundColor(colors.ContentBackgroundColor.TCell())
|
||||||
ev.titleInput.SetFieldTextColor(colors.InputFieldTextColor)
|
ev.titleInput.SetFieldTextColor(colors.InputFieldTextColor.TCell())
|
||||||
ev.titleInput.SetBorder(false)
|
ev.titleInput.SetBorder(false)
|
||||||
|
|
||||||
ev.titleInput.SetChangedFunc(func(text string) {
|
ev.titleInput.SetChangedFunc(func(text string) {
|
||||||
|
|
@ -501,9 +501,9 @@ func (ev *TaskEditView) updateValidationState() {
|
||||||
if ev.metadataBox != nil {
|
if ev.metadataBox != nil {
|
||||||
colors := config.DefaultColors()
|
colors := config.DefaultColors()
|
||||||
if len(ev.validationErrors) > 0 {
|
if len(ev.validationErrors) > 0 {
|
||||||
ev.metadataBox.SetBorderColor(colors.TaskBoxSelectedBorder)
|
ev.metadataBox.SetBorderColor(colors.TaskBoxSelectedBorder.TCell())
|
||||||
} else {
|
} else {
|
||||||
ev.metadataBox.SetBorderColor(colors.TaskBoxUnselectedBorder)
|
ev.metadataBox.SetBorderColor(colors.TaskBoxUnselectedBorder.TCell())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,9 @@ import (
|
||||||
"github.com/boolean-maybe/tiki/store"
|
"github.com/boolean-maybe/tiki/store"
|
||||||
"github.com/boolean-maybe/tiki/task"
|
"github.com/boolean-maybe/tiki/task"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: tcell import is still used for pv.pluginDef.Background/Foreground checks
|
|
||||||
|
|
||||||
// PluginView renders a filtered/sorted list of tasks across lanes
|
// PluginView renders a filtered/sorted list of tasks across lanes
|
||||||
type PluginView struct {
|
type PluginView struct {
|
||||||
root *tview.Flex
|
root *tview.Flex
|
||||||
|
|
@ -61,8 +58,8 @@ func NewPluginView(
|
||||||
|
|
||||||
func (pv *PluginView) build() {
|
func (pv *PluginView) build() {
|
||||||
// title bar with gradient background using plugin color
|
// title bar with gradient background using plugin color
|
||||||
textColor := tcell.ColorDefault
|
textColor := config.DefaultColor()
|
||||||
if pv.pluginDef.Foreground != tcell.ColorDefault {
|
if !pv.pluginDef.Foreground.IsDefault() {
|
||||||
textColor = pv.pluginDef.Foreground
|
textColor = pv.pluginDef.Foreground
|
||||||
}
|
}
|
||||||
laneNames := make([]string, len(pv.pluginDef.Lanes))
|
laneNames := make([]string, len(pv.pluginDef.Lanes))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue