From 7964d4e183a0e89a939eced7f98bba5fc4f6a9d1 Mon Sep 17 00:00:00 2001 From: ysandler Date: Sun, 25 Jan 2026 22:28:53 -0600 Subject: [PATCH] feat: UI legend --- internal/app/app.go | 13 ++++- internal/ui/legend/legend.go | 107 +++++++++++++++++++++++++++++++++++ internal/ui/styles.go | 5 ++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 internal/ui/legend/legend.go diff --git a/internal/app/app.go b/internal/app/app.go index b22a929..ee0139f 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -13,6 +13,7 @@ import ( "playback/internal/srt" "playback/internal/ui" "playback/internal/ui/header" + "playback/internal/ui/legend" "playback/internal/ui/transcript" "playback/internal/ui/waveform" ) @@ -33,6 +34,7 @@ type Model struct { // UI Components header header.Model + legend legend.Model waveform waveform.Model transcript transcript.Model @@ -56,6 +58,7 @@ func New(audioPath, transcriptPath string) Model { audioPath: audioPath, transcriptPath: transcriptPath, header: header.New(), + legend: legend.New(), waveform: waveform.New(), transcript: transcript.New(), } @@ -231,13 +234,17 @@ func (m *Model) updateLayout() { // Waveform: 5 lines waveformHeight := 5 + // Legend: 1 line + legendHeight := 1 + // Status bar: 1 line statusHeight := 1 // Transcript: remaining space - transcriptHeight := m.height - headerHeight - waveformHeight - statusHeight - 2 + transcriptHeight := m.height - headerHeight - waveformHeight - legendHeight - statusHeight - 2 m.header.SetWidth(m.width) + m.legend.SetWidth(m.width) m.waveform.SetSize(m.width, waveformHeight) m.transcript.SetSize(m.width, transcriptHeight) } @@ -287,6 +294,9 @@ func (m Model) View() string { // Transcript transcriptView := m.transcript.View() + // Legend + legendView := m.legend.View() + // Status bar statusView := m.renderStatus() @@ -296,6 +306,7 @@ func (m Model) View() string { "", waveformView, transcriptView, + legendView, statusView, ) } diff --git a/internal/ui/legend/legend.go b/internal/ui/legend/legend.go new file mode 100644 index 0000000..2d60a65 --- /dev/null +++ b/internal/ui/legend/legend.go @@ -0,0 +1,107 @@ +package legend + +import ( + "strings" + + "github.com/charmbracelet/lipgloss" + "playback/internal/ui" +) + +// Model represents the legend component +type Model struct { + Width int +} + +// New creates a new legend model +func New() Model { + return Model{} +} + +// SetWidth sets the legend width +func (m *Model) SetWidth(width int) { + m.Width = width +} + +// View renders the key legend +func (m Model) View() string { + if m.Width < 30 { + return "" // Too narrow to show legend + } + + // Group keybindings by category + groups := []struct { + title string + entries []keyEntry + }{ + { + title: "Playback", + entries: []keyEntry{ + {key: "space", desc: "play/pause"}, + {key: "q", desc: "quit"}, + {key: "?", desc: "help"}, + }, + }, + { + title: "Seeking", + entries: []keyEntry{ + {key: "h/←", desc: "seek -5s"}, + {key: "l/→", desc: "seek +5s"}, + {key: "H", desc: "seek -30s"}, + {key: "L", desc: "seek +30s"}, + {key: "g/G", desc: "start/end"}, + }, + }, + { + title: "Nav", + entries: []keyEntry{ + {key: "j/↓", desc: "next cue"}, + {key: "k/↑", desc: "prev cue"}, + {key: "ctrl+d/u", desc: "jump 5 cues"}, + }, + }, + { + title: "Edit", + entries: []keyEntry{ + {key: "i", desc: "edit transcript"}, + }, + }, + } + + // Build legend rows + var rows []string + for _, group := range groups { + rows = append(rows, renderGroup(group.title, group.entries)) + } + + return lipgloss.JoinHorizontal( + lipgloss.Left, + lipgloss.NewStyle().Width(m.Width).Render(strings.Join(rows, " │ ")), + ) +} + +type keyEntry struct { + key string + desc string +} + +func renderGroup(title string, entries []keyEntry) string { + var keyParts []string + var descParts []string + + for _, e := range entries { + keyParts = append(keyParts, ui.HelpKeyStyle.Render(e.key)) + descParts = append(descParts, ui.HelpDescStyle.Render(e.desc)) + } + + keyStr := strings.Join(keyParts, " ") + descStr := strings.Join(descParts, " ") + + return lipgloss.JoinHorizontal( + lipgloss.Left, + ui.BaseStyle.Render(title+":"), + " ", + keyStr, + " ", + descStr, + ) +} \ No newline at end of file diff --git a/internal/ui/styles.go b/internal/ui/styles.go index cd18549..ae2922a 100644 --- a/internal/ui/styles.go +++ b/internal/ui/styles.go @@ -96,6 +96,11 @@ var ( HelpDescStyle = lipgloss.NewStyle(). Foreground(ColorMuted) + // Legend styles + LegendGroupStyle = lipgloss.NewStyle(). + Foreground(ColorMuted). + PaddingLeft(2) + // Error styles ErrorStyle = lipgloss.NewStyle(). Foreground(ColorError).