diff --git a/internal/tui/model.go b/internal/tui/model.go index bba2f32..72c56c4 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -90,10 +90,10 @@ func New(options Options) Model { height: 24, focus: focusStreamers, authViewport: newAuthViewport(contentWidth(defaultViewWidth), authViewportHeight(24)), - ircViewport: newIRCViewport(detailViewportWidth(defaultViewWidth), detailViewportHeight(24)), + ircViewport: newIRCViewport(detailViewportWidth(defaultViewWidth), detailViewportHeight(24, "")), minerViewport: newMinerViewport( detailViewportWidth(defaultViewWidth), - detailViewportHeight(24), + detailViewportHeight(24, ""), ), } if options.AuthState != nil { @@ -370,9 +370,9 @@ func (m *Model) resizeComponents() { m.authViewport.Width = contentWidth(m.width) m.authViewport.Height = authViewportHeight(m.height) m.ircViewport.Width = detailViewportWidth(m.width) - m.ircViewport.Height = detailViewportHeight(m.height) + m.ircViewport.Height = detailViewportHeight(m.height, m.ircViewportContent()) m.minerViewport.Width = detailViewportWidth(m.width) - m.minerViewport.Height = detailViewportHeight(m.height) + m.minerViewport.Height = detailViewportHeight(m.height, m.minerViewportContent()) } func (m *Model) syncAuthViewport() { @@ -388,24 +388,30 @@ func (m Model) authLogContent() string { } func (m *Model) syncIRCViewport(forceBottom bool) { + stickToBottom := forceBottom || m.ircViewport.AtBottom() + content := m.ircViewportContent() + m.ircViewport.Width = detailViewportWidth(m.width) + m.ircViewport.Height = detailViewportHeight(m.height, content) if m.ircViewport.Width <= 0 || m.ircViewport.Height <= 0 { return } - stickToBottom := forceBottom || m.ircViewport.AtBottom() - m.ircViewport.SetContent(m.ircViewportContent()) + m.ircViewport.SetContent(content) if stickToBottom { m.ircViewport.GotoBottom() } } func (m *Model) syncMinerViewport(forceBottom bool) { + stickToBottom := forceBottom || m.minerViewport.AtBottom() + content := m.minerViewportContent() + m.minerViewport.Width = detailViewportWidth(m.width) + m.minerViewport.Height = detailViewportHeight(m.height, content) if m.minerViewport.Width <= 0 || m.minerViewport.Height <= 0 { return } - stickToBottom := forceBottom || m.minerViewport.AtBottom() - m.minerViewport.SetContent(m.minerViewportContent()) + m.minerViewport.SetContent(content) if stickToBottom { m.minerViewport.GotoBottom() } diff --git a/internal/tui/view.go b/internal/tui/view.go index 76b628f..6a06abf 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -9,7 +9,10 @@ import ( "parasocial/internal/twitch" ) -const dashboardPanelGap = 1 +const ( + dashboardPanelGap = 1 + detailPanelHeaderHeight = 2 +) // View renders either the login log screen or the authenticated streamer dashboard. func (m Model) View() string { @@ -43,14 +46,16 @@ func (m Model) renderStreamerView() string { rightStyle = focusedPanelStyle } + leftContent := labelStyle.Render("Streamers") + "\n" + m.renderStreamerRows(streamerListHeight(m.height)) left := leftStyle. Width(streamerListWidth(m.width)). Height(panelHeight(m.height)). - Render(labelStyle.Render("Streamers") + "\n" + m.renderStreamerRows()) + Render(leftContent) + rightContent := m.renderDetailPanel() right := rightStyle. Width(detailWidth(m.width)). Height(panelHeight(m.height)). - Render(m.renderDetailPanel()) + Render(rightContent) body := lipgloss.JoinHorizontal(lipgloss.Top, left, strings.Repeat(" ", dashboardPanelGap), right) return dashboardPageStyle.Render(header+"\n\n"+body) + "\n" @@ -142,8 +147,8 @@ func (m Model) renderMinerTab() string { return m.minerViewport.View() } -func (m Model) renderStreamerRows() string { - entries := m.visibleStreamers() +func (m Model) renderStreamerRows(maxRows int) string { + entries := m.visibleStreamers(maxRows) if len(entries) == 0 { return mutedStyle.Render("none") } @@ -163,9 +168,9 @@ func (m Model) renderStreamerRows() string { return strings.Join(lines, "\n") } -func (m Model) visibleStreamers() []twitch.StreamerEntry { +func (m Model) visibleStreamers(maxRows int) []twitch.StreamerEntry { entries := m.orderedStreamers() - visible := max(1, streamerListHeight(m.height)) + visible := max(1, maxRows) if len(entries) <= visible { return entries } @@ -276,17 +281,17 @@ func detailViewportWidth(width int) int { } func streamerListHeight(height int) int { - if height <= 0 { - return 14 - } - return max(4, height-10) + return max(1, dashboardPanelContentHeight(height)-1) } -func detailViewportHeight(height int) int { - if height <= 0 { - return 9 - } - return max(4, panelHeight(height)-6) +func detailViewportHeight(height int, content string) int { + contentHeight := max(1, lipgloss.Height(content)) + maxBodyHeight := max(1, dashboardPanelContentHeight(height)-detailPanelHeaderHeight) + return max(1, min(contentHeight, maxBodyHeight)) +} + +func dashboardPanelContentHeight(height int) int { + return max(1, panelHeight(height)-panelStyle.GetVerticalPadding()) } func panelHeight(height int) int {