diff --git a/internal/ui/common/scrollbar.go b/internal/ui/common/scrollbar.go index 7e701659348c90100534c18620f5e9949db3d050..74e384dff88192b96e15452d7cf00e556f731985 100644 --- a/internal/ui/common/scrollbar.go +++ b/internal/ui/common/scrollbar.go @@ -23,7 +23,7 @@ func Scrollbar(s *styles.Styles, height, contentSize, viewportSize, offset int) } // Calculate where the thumb starts. - trackSpace := height - thumbSize + trackSpace := height - thumbSize + 1 thumbPos := 0 if trackSpace > 0 && maxOffset > 0 { thumbPos = min(trackSpace, offset*trackSpace/maxOffset) diff --git a/internal/ui/dialog/models.go b/internal/ui/dialog/models.go index 366669f90902dfeefa63445bd61c6830e9be9f18..1336459741c4557d29b93a389a67bca93b2766b9 100644 --- a/internal/ui/dialog/models.go +++ b/internal/ui/dialog/models.go @@ -10,6 +10,7 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "charm.land/catwalk/pkg/catwalk" + "charm.land/lipgloss/v2" "github.com/charmbracelet/crush/internal/config" "github.com/charmbracelet/crush/internal/ui/common" "github.com/charmbracelet/crush/internal/ui/util" @@ -265,9 +266,14 @@ func (m *Models) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor { t.Dialog.View.GetVerticalFrameSize() m.input.SetWidth(max(0, innerWidth-t.Dialog.InputPrompt.GetHorizontalFrameSize()-1)) // (1) cursor padding - m.list.SetSize(innerWidth, height-heightOffset) m.help.SetWidth(innerWidth) + listHeight := height - heightOffset + m.list.SetSize(innerWidth, listHeight) + listTotalHeight := m.list.TotalHeight() + listWidth := max(0, innerWidth-3) // Reserve space for scrollbar. + m.list.SetSize(listWidth, listHeight) + rc := NewRenderContext(t, width) rc.Title = "Switch Model" rc.TitleInfo = m.modelTypeRadioView() @@ -281,6 +287,10 @@ func (m *Models) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor { rc.AddPart(inputView) listView := t.Dialog.List.Height(m.list.Height()).Render(m.list.Render()) + scrollbar := common.Scrollbar(t, listHeight, listTotalHeight, listHeight, m.list.Offset()) + if scrollbar != "" { + listView = lipgloss.JoinHorizontal(lipgloss.Top, listView, scrollbar) + } rc.AddPart(listView) rc.Help = m.help.View(m) diff --git a/internal/ui/list/list.go b/internal/ui/list/list.go index d474a3307ba6bff830f4702f960f31e3335a83b0..d6e01993a9c1c7d386daec38743462f6264f0c6a 100644 --- a/internal/ui/list/list.go +++ b/internal/ui/list/list.go @@ -157,6 +157,33 @@ func (l *List) Len() int { return len(l.items) } +// TotalHeight returns the total height of all items in the list. +func (l *List) TotalHeight() int { + total := 0 + for idx := range l.items { + item := l.getItem(idx) + total += item.height + if l.gap > 0 && idx < len(l.items)-1 { + total += l.gap + } + } + return total +} + +// Offset returns the current scroll offset in lines from the top. +func (l *List) Offset() int { + offset := 0 + for idx := 0; idx < l.offsetIdx; idx++ { + item := l.getItem(idx) + offset += item.height + if l.gap > 0 && idx < len(l.items)-1 { + offset += l.gap + } + } + offset += l.offsetLine + return offset +} + // lastOffsetItem returns the index and line offsets of the last item that can // be partially visible in the viewport. func (l *List) lastOffsetItem() (int, int, int) {