tiki/view/task_box.go
2026-04-10 16:07:34 -04:00

141 lines
5.1 KiB
Go

package view
import (
"fmt"
"strings"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/boolean-maybe/tiki/config"
taskpkg "github.com/boolean-maybe/tiki/task"
"github.com/boolean-maybe/tiki/util"
"github.com/boolean-maybe/tiki/util/gradient"
)
// TaskBox provides a reusable task card widget used in board and backlog views.
// applyFrameStyle applies selected/unselected styling to a frame
func applyFrameStyle(frame *tview.Frame, selected bool, colors *config.ColorConfig) {
if selected {
frame.SetBorderColor(colors.TaskBoxSelectedBorder.TCell())
} else {
frame.SetBorderColor(colors.TaskBoxUnselectedBorder.TCell())
if !colors.TaskBoxUnselectedBackground.IsDefault() {
frame.SetBackgroundColor(colors.TaskBoxUnselectedBackground.TCell())
}
}
}
// buildCompactTaskContent builds the content string for compact task display
func buildCompactTaskContent(task *taskpkg.Task, colors *config.ColorConfig, availableWidth int) string {
emoji := taskpkg.TypeEmoji(task.Type)
idGradient := gradient.RenderAdaptiveGradientText(task.ID, colors.TaskBoxIDColor, colors.FallbackTaskIDColor)
truncatedTitle := tview.Escape(util.TruncateText(task.Title, availableWidth))
priorityEmoji := taskpkg.PriorityLabel(task.Priority)
pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor)
titleTag := colors.TaskBoxTitleColor.Tag().String()
labelTag := colors.TaskBoxLabelColor.Tag().String()
return fmt.Sprintf("%s %s\n%s%s[-]\n%spriority[-] %s %spoints[-] %s%s[-]",
emoji, idGradient,
titleTag, truncatedTitle,
labelTag, priorityEmoji,
labelTag, labelTag, pointsVisual)
}
// buildExpandedTaskContent builds the content string for expanded task display
func buildExpandedTaskContent(task *taskpkg.Task, colors *config.ColorConfig, availableWidth int) string {
emoji := taskpkg.TypeEmoji(task.Type)
idGradient := gradient.RenderAdaptiveGradientText(task.ID, colors.TaskBoxIDColor, colors.FallbackTaskIDColor)
truncatedTitle := tview.Escape(util.TruncateText(task.Title, availableWidth))
// Extract first 3 lines of description
descLines := strings.Split(task.Description, "\n")
descLine1 := ""
descLine2 := ""
descLine3 := ""
if len(descLines) > 0 {
descLine1 = tview.Escape(util.TruncateText(descLines[0], availableWidth))
}
if len(descLines) > 1 {
descLine2 = tview.Escape(util.TruncateText(descLines[1], availableWidth))
}
if len(descLines) > 2 {
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
tagsStr := ""
if len(task.Tags) > 0 {
tagsStr = labelTag + "Tags:[-] " + tagValueTag + tview.Escape(util.TruncateText(strings.Join(task.Tags, ", "), availableWidth-6)) + "[-]"
}
// Build priority/points line
priorityEmoji := taskpkg.PriorityLabel(task.Priority)
pointsVisual := util.GeneratePointsVisual(task.Points, config.GetMaxPoints(), colors.PointsFilledColor, colors.PointsUnfilledColor)
priorityPointsStr := fmt.Sprintf("%spriority[-] %s %spoints[-] %s%s[-]",
labelTag, priorityEmoji,
labelTag, labelTag, pointsVisual)
return fmt.Sprintf("%s %s\n%s%s[-]\n%s%s[-]\n%s%s[-]\n%s%s[-]\n%s\n%s",
emoji, idGradient,
titleTag, truncatedTitle,
descTag, descLine1,
descTag, descLine2,
descTag, descLine3,
tagsStr, priorityPointsStr)
}
// CreateCompactTaskBox creates a compact styled task box widget (3 lines)
func CreateCompactTaskBox(task *taskpkg.Task, selected bool, colors *config.ColorConfig) *tview.Frame {
textView := tview.NewTextView().
SetDynamicColors(true).
SetWordWrap(false)
textView.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
availableWidth := width - config.TaskBoxPaddingCompact
if availableWidth < config.TaskBoxMinWidth {
availableWidth = config.TaskBoxMinWidth
}
content := buildCompactTaskContent(task, colors, availableWidth)
textView.SetText(content)
return x, y, width, height
})
frame := tview.NewFrame(textView).SetBorders(0, 0, 0, 0, 0, 0)
frame.SetBorder(true)
applyFrameStyle(frame, selected, colors)
return frame
}
// CreateExpandedTaskBox creates an expanded styled task box widget (7 lines)
func CreateExpandedTaskBox(task *taskpkg.Task, selected bool, colors *config.ColorConfig) *tview.Frame {
textView := tview.NewTextView().
SetDynamicColors(true).
SetWordWrap(false)
textView.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
availableWidth := width - config.TaskBoxPaddingExpanded // less overhead for multiline
if availableWidth < config.TaskBoxMinWidth {
availableWidth = config.TaskBoxMinWidth
}
content := buildExpandedTaskContent(task, colors, availableWidth)
textView.SetText(content)
return x, y, width, height
})
frame := tview.NewFrame(textView).SetBorders(0, 0, 0, 0, 0, 0)
frame.SetBorder(true)
applyFrameStyle(frame, selected, colors)
return frame
}