diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index fcc3990..56da29a 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -29,6 +29,9 @@ const ( ) type model struct { + width int + height int + ctx *lmcli.Context convShortname string @@ -135,9 +138,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height m.content.Width = msg.Width - m.content.Height = msg.Height - m.input.Height() - lipgloss.Height(m.footerView()) - lipgloss.Height(m.headerView()) - m.input.SetWidth(msg.Width - 1) + m.content.Height = msg.Height - m.getFixedComponentHeight() + m.input.SetWidth(msg.Width-1) m.updateContent() case msgConversationLoaded: m.conversation = (*models.Conversation)(msg) @@ -202,6 +207,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, wrapError(err)) } } + case msgError: + m.err = error(msg) } if len(cmds) > 0 { @@ -229,15 +236,45 @@ func (m model) View() string { // without this, the below view functions may do weird things return "" } + + m.content.Height = m.height - m.getFixedComponentHeight() + + sections := make([]string, 0, 6) + error := m.errorView() + scrollbar := m.scrollbarView() + sections = append(sections, m.headerView()) + if scrollbar != "" { + sections = append(sections, scrollbar) + } + sections = append(sections, m.contentView()) + if error != "" { + sections = append(sections, error) + } + sections = append(sections, m.inputView()) + sections = append(sections, m.footerView()) + return lipgloss.JoinVertical( lipgloss.Left, - m.headerView(), - m.contentView(), - m.inputView(), - m.footerView(), + sections..., ) } +func (m *model) getFixedComponentHeight() int { + h := 0 + h += m.input.Height() + h += lipgloss.Height(m.headerView()) + h += lipgloss.Height(m.footerView()) + scrollbar := m.scrollbarView() + if scrollbar != "" { + h += lipgloss.Height(scrollbar) + } + errorView := m.errorView() + if errorView != "" { + h += lipgloss.Height(errorView) + } + return h +} + func (m *model) headerView() string { titleStyle := lipgloss.NewStyle(). PaddingLeft(1). @@ -258,6 +295,32 @@ func (m *model) contentView() string { return m.content.View() } +func (m *model) errorView() string { + if m.err == nil { + return "" + } + return lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("1")). + Width(m.content.Width). + AlignHorizontal(lipgloss.Center). + Render(fmt.Sprintf("%s", m.err)) +} + +func (m *model) scrollbarView() string { + if m.content.AtTop() { + return "" + } + + count := int(m.content.ScrollPercent() * float64(m.content.Width-2)) + fill := strings.Repeat("-", count) + return lipgloss.NewStyle(). + PaddingLeft(1). + PaddingRight(1). + Width(m.content.Width). + Render(fill) +} + func (m *model) inputView() string { return m.input.View() } @@ -284,8 +347,8 @@ func (m *model) footerView() string { left := strings.Join(leftSegments, segmentSeparator) right := strings.Join(rightSegments, segmentSeparator) - totalWidth := lipgloss.Width(left + right) - remaining := m.content.Width - totalWidth + totalWidth := lipgloss.Width(left) + lipgloss.Width(right) + remaining := m.width - totalWidth var padding string if remaining > 0 { @@ -295,7 +358,7 @@ func (m *model) footerView() string { footer := left + padding + right if remaining < 0 { ellipses := "... " - footer = footer[:m.content.Width-len(ellipses)] + ellipses + footer = footer[:m.width-len(ellipses)] + ellipses } return footerStyle.Render(footer) }