feat: UI legend
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
|||||||
"playback/internal/srt"
|
"playback/internal/srt"
|
||||||
"playback/internal/ui"
|
"playback/internal/ui"
|
||||||
"playback/internal/ui/header"
|
"playback/internal/ui/header"
|
||||||
|
"playback/internal/ui/legend"
|
||||||
"playback/internal/ui/transcript"
|
"playback/internal/ui/transcript"
|
||||||
"playback/internal/ui/waveform"
|
"playback/internal/ui/waveform"
|
||||||
)
|
)
|
||||||
@@ -33,6 +34,7 @@ type Model struct {
|
|||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
header header.Model
|
header header.Model
|
||||||
|
legend legend.Model
|
||||||
waveform waveform.Model
|
waveform waveform.Model
|
||||||
transcript transcript.Model
|
transcript transcript.Model
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ func New(audioPath, transcriptPath string) Model {
|
|||||||
audioPath: audioPath,
|
audioPath: audioPath,
|
||||||
transcriptPath: transcriptPath,
|
transcriptPath: transcriptPath,
|
||||||
header: header.New(),
|
header: header.New(),
|
||||||
|
legend: legend.New(),
|
||||||
waveform: waveform.New(),
|
waveform: waveform.New(),
|
||||||
transcript: transcript.New(),
|
transcript: transcript.New(),
|
||||||
}
|
}
|
||||||
@@ -231,13 +234,17 @@ func (m *Model) updateLayout() {
|
|||||||
// Waveform: 5 lines
|
// Waveform: 5 lines
|
||||||
waveformHeight := 5
|
waveformHeight := 5
|
||||||
|
|
||||||
|
// Legend: 1 line
|
||||||
|
legendHeight := 1
|
||||||
|
|
||||||
// Status bar: 1 line
|
// Status bar: 1 line
|
||||||
statusHeight := 1
|
statusHeight := 1
|
||||||
|
|
||||||
// Transcript: remaining space
|
// Transcript: remaining space
|
||||||
transcriptHeight := m.height - headerHeight - waveformHeight - statusHeight - 2
|
transcriptHeight := m.height - headerHeight - waveformHeight - legendHeight - statusHeight - 2
|
||||||
|
|
||||||
m.header.SetWidth(m.width)
|
m.header.SetWidth(m.width)
|
||||||
|
m.legend.SetWidth(m.width)
|
||||||
m.waveform.SetSize(m.width, waveformHeight)
|
m.waveform.SetSize(m.width, waveformHeight)
|
||||||
m.transcript.SetSize(m.width, transcriptHeight)
|
m.transcript.SetSize(m.width, transcriptHeight)
|
||||||
}
|
}
|
||||||
@@ -287,6 +294,9 @@ func (m Model) View() string {
|
|||||||
// Transcript
|
// Transcript
|
||||||
transcriptView := m.transcript.View()
|
transcriptView := m.transcript.View()
|
||||||
|
|
||||||
|
// Legend
|
||||||
|
legendView := m.legend.View()
|
||||||
|
|
||||||
// Status bar
|
// Status bar
|
||||||
statusView := m.renderStatus()
|
statusView := m.renderStatus()
|
||||||
|
|
||||||
@@ -296,6 +306,7 @@ func (m Model) View() string {
|
|||||||
"",
|
"",
|
||||||
waveformView,
|
waveformView,
|
||||||
transcriptView,
|
transcriptView,
|
||||||
|
legendView,
|
||||||
statusView,
|
statusView,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
107
internal/ui/legend/legend.go
Normal file
107
internal/ui/legend/legend.go
Normal file
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -96,6 +96,11 @@ var (
|
|||||||
HelpDescStyle = lipgloss.NewStyle().
|
HelpDescStyle = lipgloss.NewStyle().
|
||||||
Foreground(ColorMuted)
|
Foreground(ColorMuted)
|
||||||
|
|
||||||
|
// Legend styles
|
||||||
|
LegendGroupStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorMuted).
|
||||||
|
PaddingLeft(2)
|
||||||
|
|
||||||
// Error styles
|
// Error styles
|
||||||
ErrorStyle = lipgloss.NewStyle().
|
ErrorStyle = lipgloss.NewStyle().
|
||||||
Foreground(ColorError).
|
Foreground(ColorError).
|
||||||
|
|||||||
Reference in New Issue
Block a user