fix: cream background fills entire terminal, bottom bar pinned to bottom
- PadLine now wraps entire line content with Background(ScreenBg) so cream color shows behind all text, not just the right-edge padding - Added PadLineWithBg for active row warm highlight (#f8f0e6) - Restructured View() to pin bottom bar at terminal bottom with blank fill between file list and footer - Removed 'd remove' from bottom bar keybindings to match web reference
This commit is contained in:
@@ -18,7 +18,7 @@ var (
|
|||||||
Teal = lipgloss.Color("#2dd4bf")
|
Teal = lipgloss.Color("#2dd4bf")
|
||||||
|
|
||||||
Cream = lipgloss.Color("#fdf6ef")
|
Cream = lipgloss.Color("#fdf6ef")
|
||||||
Warm = lipgloss.Color("#faf0e6")
|
Warm = lipgloss.Color("#f8f0e6")
|
||||||
Peach = lipgloss.Color("#fce8d5")
|
Peach = lipgloss.Color("#fce8d5")
|
||||||
|
|
||||||
Dark = lipgloss.Color("#2d1f14")
|
Dark = lipgloss.Color("#2d1f14")
|
||||||
@@ -149,18 +149,22 @@ var (
|
|||||||
Bold(true)
|
Bold(true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// PadLine pads a single rendered line to the given width with the screen
|
// PadLine pads a single rendered line to the full terminal width and applies
|
||||||
// background color. This ensures every line carries the background color
|
// the cream background behind ALL content (not just the padding). This is
|
||||||
// all the way to the right edge of the terminal.
|
// achieved by placing the line inside a full-width style with Background set.
|
||||||
func PadLine(line string, width int) string {
|
func PadLine(line string, width int) string {
|
||||||
|
return PadLineWithBg(line, width, ScreenBg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PadLineWithBg pads a line to full width with a specific background color.
|
||||||
|
func PadLineWithBg(line string, width int, bg color.Color) string {
|
||||||
w := lipgloss.Width(line)
|
w := lipgloss.Width(line)
|
||||||
if w >= width {
|
if w >= width {
|
||||||
return line
|
// Even if the line is already wide enough, wrap with background
|
||||||
|
return lipgloss.NewStyle().Background(bg).Render(line)
|
||||||
}
|
}
|
||||||
pad := lipgloss.NewStyle().
|
pad := strings.Repeat(" ", width-w)
|
||||||
Background(ScreenBg).
|
return lipgloss.NewStyle().Background(bg).Render(line + pad)
|
||||||
Render(strings.Repeat(" ", width-w))
|
|
||||||
return line + pad
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillBlankLines returns n blank lines fully painted with the screen
|
// FillBlankLines returns n blank lines fully painted with the screen
|
||||||
|
|||||||
+73
-53
@@ -31,51 +31,85 @@ func nameWidth(termW int) int {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pad is a shortcut that pads a line to full width with cream background.
|
||||||
|
func (m Model) pad(line string) string {
|
||||||
|
return theme.PadLine(line, m.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
// padWarm pads a line to full width with the warm (highlighted) background.
|
||||||
|
func (m Model) padWarm(line string) string {
|
||||||
|
return theme.PadLineWithBg(line, m.width, theme.Warm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blank returns a full-width blank line with cream background.
|
||||||
|
func (m Model) blank() string {
|
||||||
|
return theme.PadLine("", m.width)
|
||||||
|
}
|
||||||
|
|
||||||
// View renders the entire TUI, filling the full terminal.
|
// View renders the entire TUI, filling the full terminal.
|
||||||
func (m Model) View() string {
|
func (m Model) View() string {
|
||||||
if m.width == 0 || m.height == 0 {
|
if m.width == 0 || m.height == 0 {
|
||||||
return "Loading..."
|
return "Loading..."
|
||||||
}
|
}
|
||||||
|
|
||||||
var sections []string
|
// We build an array of pre-padded lines (each already full-width with bg).
|
||||||
|
var lines []string
|
||||||
|
|
||||||
sections = append(sections, m.renderTitleBar())
|
// Title bar (1 line)
|
||||||
sections = append(sections, m.renderDivider())
|
lines = append(lines, m.pad(m.renderTitleBar()))
|
||||||
|
|
||||||
|
// Divider (1 line)
|
||||||
|
lines = append(lines, m.pad(m.renderDivider()))
|
||||||
|
|
||||||
|
// State-specific content
|
||||||
|
var bottomLines []string // lines that go at the very bottom
|
||||||
|
|
||||||
switch m.state {
|
switch m.state {
|
||||||
case stateFileList:
|
case stateFileList:
|
||||||
sections = append(sections, m.renderColumnHeader())
|
// Column header
|
||||||
sections = append(sections, m.renderFileList())
|
lines = append(lines, m.pad(m.renderColumnHeader()))
|
||||||
sections = append(sections, m.renderDivider())
|
|
||||||
sections = append(sections, m.renderBottomBar())
|
// File rows
|
||||||
|
fileLines := m.renderFileRows()
|
||||||
|
lines = append(lines, fileLines...)
|
||||||
|
|
||||||
|
// Bottom section: divider + bottom bar (pinned to bottom)
|
||||||
|
bottomLines = append(bottomLines, m.pad(m.renderDivider()))
|
||||||
|
bottomLines = append(bottomLines, m.pad(m.renderBottomBar()))
|
||||||
|
|
||||||
case stateConverting:
|
case stateConverting:
|
||||||
sections = append(sections, m.renderConverting())
|
for _, l := range strings.Split(m.renderConverting(), "\n") {
|
||||||
|
lines = append(lines, m.pad(l))
|
||||||
|
}
|
||||||
|
|
||||||
case stateResults:
|
case stateResults:
|
||||||
sections = append(sections, m.renderResults())
|
for _, l := range strings.Split(m.renderResults(), "\n") {
|
||||||
sections = append(sections, m.renderDivider())
|
lines = append(lines, m.pad(l))
|
||||||
sections = append(sections, m.renderResultsFooter())
|
}
|
||||||
|
bottomLines = append(bottomLines, m.pad(m.renderDivider()))
|
||||||
|
bottomLines = append(bottomLines, m.pad(m.renderResultsFooter()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help overlay (if visible, goes right after content)
|
||||||
if m.showHelp {
|
if m.showHelp {
|
||||||
sections = append(sections, m.renderHelp())
|
for _, l := range strings.Split(m.renderHelp(), "\n") {
|
||||||
|
lines = append(lines, m.pad(l))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content := lipgloss.JoinVertical(lipgloss.Left, sections...)
|
// Calculate how many blank lines we need between content and bottom bar
|
||||||
|
totalUsed := len(lines) + len(bottomLines)
|
||||||
// Split into individual lines and pad each to full width with background
|
remaining := m.height - totalUsed
|
||||||
lines := strings.Split(content, "\n")
|
|
||||||
for i, line := range lines {
|
|
||||||
lines[i] = theme.PadLine(line, m.width)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill remaining vertical space with background-colored blank lines
|
|
||||||
remaining := m.height - len(lines)
|
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
fill := theme.FillBlankLines(remaining, m.width)
|
for i := 0; i < remaining; i++ {
|
||||||
return strings.Join(lines, "\n") + "\n" + fill
|
lines = append(lines, m.blank())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate if content exceeds terminal height
|
// Append bottom bar lines
|
||||||
|
lines = append(lines, bottomLines...)
|
||||||
|
|
||||||
|
// Truncate if somehow we exceed terminal height
|
||||||
if len(lines) > m.height {
|
if len(lines) > m.height {
|
||||||
lines = lines[:m.height]
|
lines = lines[:m.height]
|
||||||
}
|
}
|
||||||
@@ -122,7 +156,6 @@ func (m Model) renderDivider() string {
|
|||||||
func (m Model) renderColumnHeader() string {
|
func (m Model) renderColumnHeader() string {
|
||||||
nw := nameWidth(m.width)
|
nw := nameWidth(m.width)
|
||||||
|
|
||||||
// "Name" sits after the cursor+check prefix (6 chars), indented
|
|
||||||
nameHdr := theme.Breadcrumb.Copy().Width(colCursor + colCheck + nw).Render(
|
nameHdr := theme.Breadcrumb.Copy().Width(colCursor + colCheck + nw).Render(
|
||||||
strings.Repeat(" ", colCursor+colCheck) + "Name")
|
strings.Repeat(" ", colCursor+colCheck) + "Name")
|
||||||
sizeHdr := theme.Breadcrumb.Copy().Width(colSize).Align(lipgloss.Right).Render("Size")
|
sizeHdr := theme.Breadcrumb.Copy().Width(colSize).Align(lipgloss.Right).Render("Size")
|
||||||
@@ -132,16 +165,16 @@ func (m Model) renderColumnHeader() string {
|
|||||||
return nameHdr + " " + sizeHdr + " " + fmtHdr + " " + statHdr
|
return nameHdr + " " + sizeHdr + " " + fmtHdr + " " + statHdr
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── File list ───────────────────────────────────────────────
|
// ─── File rows ───────────────────────────────────────────────
|
||||||
|
|
||||||
func (m Model) renderFileList() string {
|
// renderFileRows returns already-padded lines for the file list area.
|
||||||
|
func (m Model) renderFileRows() []string {
|
||||||
if len(m.files) == 0 {
|
if len(m.files) == 0 {
|
||||||
empty := lipgloss.NewStyle().
|
empty := lipgloss.NewStyle().
|
||||||
Foreground(theme.Light).
|
Foreground(theme.Light).
|
||||||
Italic(true).
|
Italic(true).
|
||||||
Padding(2, 4).
|
|
||||||
Render(" No supported files found. Pass file paths or glob patterns as arguments.")
|
Render(" No supported files found. Pass file paths or glob patterns as arguments.")
|
||||||
return empty
|
return []string{m.blank(), m.pad(empty), m.blank()}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxVisible := m.maxVisibleFiles()
|
maxVisible := m.maxVisibleFiles()
|
||||||
@@ -152,26 +185,23 @@ func (m Model) renderFileList() string {
|
|||||||
|
|
||||||
var rows []string
|
var rows []string
|
||||||
for i := m.scroll; i < end; i++ {
|
for i := m.scroll; i < end; i++ {
|
||||||
rows = append(rows, m.renderFileRow(i))
|
row := m.renderFileRow(i)
|
||||||
}
|
isCursor := i == m.cursor
|
||||||
|
if isCursor {
|
||||||
// Pad with empty rows so the file list always fills the available space
|
rows = append(rows, m.padWarm(row))
|
||||||
rendered := len(rows)
|
} else {
|
||||||
if rendered < maxVisible {
|
rows = append(rows, m.pad(row))
|
||||||
emptyRow := strings.Repeat(" ", m.width)
|
|
||||||
for i := rendered; i < maxVisible; i++ {
|
|
||||||
rows = append(rows, emptyRow)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrollbar indicator
|
// Scrollbar indicator (if needed)
|
||||||
if len(m.files) > maxVisible {
|
if len(m.files) > maxVisible {
|
||||||
scrollInfo := theme.Help.Render(fmt.Sprintf(
|
scrollInfo := theme.Help.Render(fmt.Sprintf(
|
||||||
" showing %d\u2013%d of %d", m.scroll+1, end, len(m.files)))
|
" showing %d\u2013%d of %d", m.scroll+1, end, len(m.files)))
|
||||||
rows = append(rows, scrollInfo)
|
rows = append(rows, m.pad(scrollInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, rows...)
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) renderFileRow(idx int) string {
|
func (m Model) renderFileRow(idx int) string {
|
||||||
@@ -197,7 +227,6 @@ func (m Model) renderFileRow(idx int) string {
|
|||||||
extBadge := theme.ExtBadge(catColor).Render(strings.ToUpper(f.ext))
|
extBadge := theme.ExtBadge(catColor).Render(strings.ToUpper(f.ext))
|
||||||
|
|
||||||
nameText := f.name
|
nameText := f.name
|
||||||
// Reserve space for icon(2) + space(1) + ext(max 5) + space(1) + ...
|
|
||||||
maxName := nw - 10
|
maxName := nw - 10
|
||||||
if maxName < 8 {
|
if maxName < 8 {
|
||||||
maxName = 8
|
maxName = 8
|
||||||
@@ -213,7 +242,6 @@ func (m Model) renderFileRow(idx int) string {
|
|||||||
nameStyle = theme.FileName
|
nameStyle = theme.FileName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the name cell content, then wrap in a fixed-width style
|
|
||||||
nameContent := icon + " " + extBadge + " " + nameStyle.Render(nameText)
|
nameContent := icon + " " + extBadge + " " + nameStyle.Render(nameText)
|
||||||
nameCell := lipgloss.NewStyle().Width(nw).MaxWidth(nw).Render(nameContent)
|
nameCell := lipgloss.NewStyle().Width(nw).MaxWidth(nw).Render(nameContent)
|
||||||
|
|
||||||
@@ -238,15 +266,7 @@ func (m Model) renderFileRow(idx int) string {
|
|||||||
}
|
}
|
||||||
statusCell := lipgloss.NewStyle().Width(colStatus).Align(lipgloss.Center).Render(statusStr)
|
statusCell := lipgloss.NewStyle().Width(colStatus).Align(lipgloss.Center).Render(statusStr)
|
||||||
|
|
||||||
// ── Assemble row ──
|
return cursor + check + nameCell + " " + sizeCell + " " + fmtCell + " " + statusCell
|
||||||
row := cursor + check + nameCell + " " + sizeCell + " " + fmtCell + " " + statusCell
|
|
||||||
|
|
||||||
// Highlight active row with warm background
|
|
||||||
if isCursor {
|
|
||||||
row = lipgloss.NewStyle().Background(theme.Warm).Render(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
return row
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderFormatSelector(f fileEntry, active bool) string {
|
func renderFormatSelector(f fileEntry, active bool) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user