From af9d0ecf0e8e114dde33468755ee05b1f2ef44e7 Mon Sep 17 00:00:00 2001 From: Nicholas Zambetti Date: Wed, 6 May 2026 20:26:16 +0200 Subject: [PATCH 1/3] Update themes to respect responsive height --- ui/model/keys.go | 48 ++++++++++++++++++++++++++++++----- ui/model/overlays.go | 53 +++++++++++++++++++++++++++++++++++++++ ui/model/state.go | 1 + ui/model/view_overlays.go | 14 +++++------ 4 files changed, 103 insertions(+), 13 deletions(-) diff --git a/ui/model/keys.go b/ui/model/keys.go index 062f334a..7f50f364 100644 --- a/ui/model/keys.go +++ b/ui/model/keys.go @@ -1632,24 +1632,60 @@ func (m *Model) handleThemeKey(msg tea.KeyPressMsg) tea.Cmd { case "ctrl+c": m.themePickerCancel() return m.quit() + case "up", "k": if m.themePicker.cursor > 0 { m.themePicker.cursor-- - m.themePickerApply() // live preview - } else { + } else if count > 0 { m.themePicker.cursor = count - 1 - m.themePickerApply() // live preview } + m.themePickerApply() + m.themePickerMaybeAdjustScroll(m.themePickerVisible()) + case "down", "j": if m.themePicker.cursor < count-1 { m.themePicker.cursor++ - m.themePickerApply() // live preview - } else { + } else if count > 0 { m.themePicker.cursor = 0 - m.themePickerApply() // live preview } + m.themePickerApply() + m.themePickerMaybeAdjustScroll(m.themePickerVisible()) + + case "ctrl+x": + m.toggleExpandPlaylist() + m.themePickerMaybeAdjustScroll(m.themePickerVisible()) + + case "pgup", "ctrl+u": + if m.themePicker.cursor > 0 { + visible := m.themePickerVisible() + m.themePicker.cursor -= min(m.themePicker.cursor, visible) + m.themePickerApply() + m.themePickerMaybeAdjustScroll(visible) + } + + case "pgdown", "ctrl+d": + if m.themePicker.cursor < count-1 { + visible := m.themePickerVisible() + m.themePicker.cursor = min(count-1, m.themePicker.cursor+visible) + m.themePickerApply() + m.themePickerMaybeAdjustScroll(visible) + } + + case "home", "g": + m.themePicker.cursor = 0 + m.themePickerApply() + m.themePickerMaybeAdjustScroll(m.themePickerVisible()) + + case "end", "G": + if count > 0 { + m.themePicker.cursor = count - 1 + } + m.themePickerApply() + m.themePickerMaybeAdjustScroll(m.themePickerVisible()) + case "enter": m.themePickerSelect() + case "esc", "q", "t": m.themePickerCancel() } diff --git a/ui/model/overlays.go b/ui/model/overlays.go index 23a21696..3d84c17e 100644 --- a/ui/model/overlays.go +++ b/ui/model/overlays.go @@ -3,7 +3,10 @@ package model import ( "strings" + "charm.land/lipgloss/v2" + "cliamp/theme" + "cliamp/ui" ) // openThemePicker re-loads themes from disk (picking up new user files) @@ -15,6 +18,8 @@ func (m *Model) openThemePicker() { // Position cursor on the currently active theme. // Picker list: 0 = Default, 1..N = themes[0..N-1] m.themePicker.cursor = m.themeIdx + 1 + m.themePicker.scroll = 0 + m.themePickerMaybeAdjustScroll(m.themePickerVisible()) } // themePickerApply applies the theme under the cursor for live preview. @@ -45,6 +50,54 @@ func (m *Model) themePickerCancel() { m.themePicker.visible = false } +func (m *Model) themePickerHelpLine() string { + return helpKey("↓↑", "Scroll ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close") +} + +func (m *Model) themePickerVisible() int { + probeSections := []string{ + titleStyle.Render("T H E M E S"), + "", + "x", // 1-line list placeholder + "", + dimStyle.Render(" 0/0 themes"), + "", + m.themePickerHelpLine(), + } + + probeFrame := ui.FrameStyle.Render(strings.Join(probeSections, "\n")) + fixedHeight := lipgloss.Height(probeFrame) - 1 + + limit := maxPlVisible + if m.heightExpanded { + limit = m.height + } + return max(3, min(limit, m.height-fixedHeight)) +} + +func (m *Model) themePickerMaybeAdjustScroll(visible int) { + if visible <= 0 { + return + } + count := len(m.themes) + 1 + if m.themePicker.cursor < 0 { + m.themePicker.cursor = 0 + } + if m.themePicker.cursor >= count && count > 0 { + m.themePicker.cursor = count - 1 + } + + if m.themePicker.cursor < m.themePicker.scroll { + m.themePicker.scroll = m.themePicker.cursor + } else if m.themePicker.cursor >= m.themePicker.scroll+visible { + m.themePicker.scroll = m.themePicker.cursor - visible + 1 + } + + if m.themePicker.scroll+visible > count && count > 0 { + m.themePicker.scroll = max(0, count-visible) + } +} + // openPlaylistManager loads playlist metadata and opens the manager overlay. func (m *Model) openPlaylistManager() { m.plMgrResetFilter() diff --git a/ui/model/state.go b/ui/model/state.go index b0c18537..7f54f3a2 100644 --- a/ui/model/state.go +++ b/ui/model/state.go @@ -65,6 +65,7 @@ type seekState struct { type themePickerState struct { visible bool cursor int + scroll int savedIdx int // themeIdx before opening picker, for cancel/restore } diff --git a/ui/model/view_overlays.go b/ui/model/view_overlays.go index 00a37ae4..8db6ae8a 100644 --- a/ui/model/view_overlays.go +++ b/ui/model/view_overlays.go @@ -67,8 +67,9 @@ func (m Model) renderThemePicker() string { } count := len(m.themes) + 1 - maxVisible := 15 - scroll := scrollStart(m.themePicker.cursor, maxVisible) + maxVisible := m.themePickerVisible() + scroll := m.themePicker.scroll + rendered := 0 for i := scroll; i < count && i < scroll+maxVisible; i++ { var name string @@ -78,13 +79,12 @@ func (m Model) renderThemePicker() string { name = m.themes[i-1].Name } lines = append(lines, cursorLine(name, i == m.themePicker.cursor)) + rendered++ } - if count > maxVisible { - lines = append(lines, "", dimStyle.Render(fmt.Sprintf(" %d/%d themes", m.themePicker.cursor+1, count))) - } - - lines = append(lines, "", helpKey("↓↑", "Scroll ")+helpKey("Enter", "Select ")+helpKey("Esc", "Cancel")) + lines = padLines(lines, maxVisible, rendered) + lines = append(lines, "", dimStyle.Render(fmt.Sprintf(" %d/%d themes", m.themePicker.cursor+1, count))) + lines = append(lines, "", m.themePickerHelpLine()) return m.centerOverlay(strings.Join(lines, "\n")) } From 4c5c22968971dc827a39af01a2ad2015b811c973 Mon Sep 17 00:00:00 2001 From: Nicholas Zambetti Date: Wed, 6 May 2026 20:59:22 +0200 Subject: [PATCH 2/3] Update lyrics to respect responsive height --- ui/model/keys.go | 2 ++ ui/model/lyrics.go | 10 ++++++++++ ui/model/view_overlays.go | 8 +++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ui/model/keys.go b/ui/model/keys.go index 062f334a..a4e125d9 100644 --- a/ui/model/keys.go +++ b/ui/model/keys.go @@ -252,6 +252,8 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd { m.lyrics.scroll++ } } + case "ctrl+x": + m.toggleExpandPlaylist() } return nil } diff --git a/ui/model/lyrics.go b/ui/model/lyrics.go index c78b7dd2..081eb012 100644 --- a/ui/model/lyrics.go +++ b/ui/model/lyrics.go @@ -54,3 +54,13 @@ func (m *Model) lyricsHaveTimestamps() bool { } return false } + +// lyricsVisibleHeight returns the number of lyrics lines to show. +func (m Model) lyricsVisibleHeight() int { + limit := maxPlVisible + if m.heightExpanded { + limit = m.height + } + // Fixed overhead: header (2) + spacing/footer (2) = 4. + return max(3, min(limit, m.height-4)) +} diff --git a/ui/model/view_overlays.go b/ui/model/view_overlays.go index 00a37ae4..8375c104 100644 --- a/ui/model/view_overlays.go +++ b/ui/model/view_overlays.go @@ -496,6 +496,7 @@ func (m Model) renderURLInputOverlay() string { } func (m Model) renderLyricsOverlay() string { + visible := m.lyricsVisibleHeight() lines := []string{ titleStyle.Render("L Y R I C S"), "", @@ -532,7 +533,6 @@ func (m Model) renderLyricsOverlay() string { } } - visible := max(m.height-8, 5) half := visible / 2 startIdx := max(activeIdx-half, 0) endIdx := startIdx + visible @@ -554,7 +554,6 @@ func (m Model) renderLyricsOverlay() string { } } else { // Scroll mode: manual navigation with j/k or arrow keys. - visible := max(m.height-8, 5) endIdx := min(m.lyrics.scroll+visible, len(m.lyrics.lines)) for i := m.lyrics.scroll; i < endIdx; i++ { @@ -566,9 +565,8 @@ func (m Model) renderLyricsOverlay() string { } } - for len(lines) < 14 { - lines = append(lines, "") - } + rendered := len(lines) - 2 // -2 for header and spacing + lines = padLines(lines, visible, rendered) if m.lyricsSyncable() && m.lyricsHaveTimestamps() { lines = append(lines, "", helpKey("Esc", "Close")) From 3d6591f0de6dfb4345e03439f4e03481cdb97032 Mon Sep 17 00:00:00 2001 From: Nicholas Zambetti Date: Wed, 6 May 2026 22:42:32 +0200 Subject: [PATCH 3/3] Rename toggleExpandPlaylist to generalized toggleExpandedView --- ui/model/filebrowser.go | 2 +- ui/model/keymap.go | 2 +- ui/model/keys.go | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/model/filebrowser.go b/ui/model/filebrowser.go index 75686929..6f7c1c2a 100644 --- a/ui/model/filebrowser.go +++ b/ui/model/filebrowser.go @@ -322,7 +322,7 @@ func (m *Model) handleFileBrowserKey(msg tea.KeyPressMsg) tea.Cmd { m.fileBrowser.filtered = nil case "ctrl+x": - m.toggleExpandPlaylist() + m.toggleExpandedView() m.fbMaybeAdjustScroll(m.fbVisible()) case "/": diff --git a/ui/model/keymap.go b/ui/model/keymap.go index 2d79832a..f688d3a4 100644 --- a/ui/model/keymap.go +++ b/ui/model/keymap.go @@ -333,7 +333,7 @@ func (m *Model) handleKeymapKey(msg tea.KeyPressMsg) tea.Cmd { m.keymapMaybeAdjustScroll(m.keymapVisible()) case "ctrl+x": - m.toggleExpandPlaylist() + m.toggleExpandedView() m.keymapMaybeAdjustScroll(m.keymapVisible()) case "pgup", "ctrl+u": diff --git a/ui/model/keys.go b/ui/model/keys.go index 688d42c1..9705e5d4 100644 --- a/ui/model/keys.go +++ b/ui/model/keys.go @@ -253,7 +253,7 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd { } } case "ctrl+x": - m.toggleExpandPlaylist() + m.toggleExpandedView() } return nil } @@ -360,7 +360,7 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd { case "R": return m.switchToProvider("radio") case "ctrl+x": - m.toggleExpandPlaylist() + m.toggleExpandedView() case "ctrl+f": m.openProviderSearch() } @@ -750,7 +750,7 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd { case "ctrl+x": if m.focus == focusPlaylist { - m.toggleExpandPlaylist() + m.toggleExpandedView() } case "x": @@ -1025,8 +1025,8 @@ func (m *Model) updateProvSearch() { } } -// toggleExpandPlaylist toggles the playlist panel between default and expanded height. -func (m *Model) toggleExpandPlaylist() { +// toggleExpandedView toggles the UI between default and expanded height. +func (m *Model) toggleExpandedView() { m.heightExpanded = !m.heightExpanded m.applyHeightMode() m.adjustScroll() @@ -1654,7 +1654,7 @@ func (m *Model) handleThemeKey(msg tea.KeyPressMsg) tea.Cmd { m.themePickerMaybeAdjustScroll(m.themePickerVisible()) case "ctrl+x": - m.toggleExpandPlaylist() + m.toggleExpandedView() m.themePickerMaybeAdjustScroll(m.themePickerVisible()) case "pgup", "ctrl+u":