diff --git a/internal/ui/model/header.go b/internal/ui/model/header.go index e01a19143c20e0d3e2c6753b719c28092077ac91..5e704bf6ed8a5f69e224ceeca05d34ad59740789 100644 --- a/internal/ui/model/header.go +++ b/internal/ui/model/header.go @@ -12,6 +12,7 @@ import ( "github.com/charmbracelet/crush/internal/session" "github.com/charmbracelet/crush/internal/ui/common" "github.com/charmbracelet/crush/internal/ui/styles" + uv "github.com/charmbracelet/ultraviolet" "github.com/charmbracelet/x/ansi" ) @@ -22,29 +23,58 @@ const ( rightPadding = 1 ) -// renderCompactHeader renders the compact header for the given session. -func renderCompactHeader( - com *common.Common, +type header struct { + // cached logo and compact logo + logo string + compactLogo string + + com *common.Common + width int + compact bool +} + +// newHeader creates a new header model. +func newHeader(com *common.Common) *header { + h := &header{ + com: com, + } + t := com.Styles + h.compactLogo = t.Header.Charm.Render("Charmâ„¢") + " " + + styles.ApplyBoldForegroundGrad(t, "CRUSH", t.Secondary, t.Primary) + " " + return h +} + +// drawHeader draws the header for the given session. +func (h *header) drawHeader( + scr uv.Screen, + area uv.Rectangle, session *session.Session, - lspClients *csync.Map[string, *lsp.Client], + compact bool, detailsOpen bool, width int, -) string { - if session == nil || session.ID == "" { - return "" +) { + t := h.com.Styles + if width != h.width || compact != h.compact { + h.logo = renderLogo(h.com.Styles, compact, width) } - t := com.Styles + h.width = width + h.compact = compact - var b strings.Builder + if !compact || session == nil || h.com.App == nil { + uv.NewStyledString(h.logo).Draw(scr, area) + return + } + + if session.ID == "" { + return + } - b.WriteString(t.Header.Charm.Render("Charmâ„¢")) - b.WriteString(" ") - b.WriteString(styles.ApplyBoldForegroundGrad(t, "CRUSH", t.Secondary, t.Primary)) - b.WriteString(" ") + var b strings.Builder + b.WriteString(h.compactLogo) availDetailWidth := width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minHeaderDiags - details := renderHeaderDetails(com, session, lspClients, detailsOpen, availDetailWidth) + details := renderHeaderDetails(h.com, session, h.com.App.LSPClients, detailsOpen, availDetailWidth) remainingWidth := width - lipgloss.Width(b.String()) - @@ -61,7 +91,9 @@ func renderCompactHeader( b.WriteString(details) - return t.Base.Padding(0, rightPadding, 0, leftPadding).Render(b.String()) + view := uv.NewStyledString( + t.Base.Padding(0, rightPadding, 0, leftPadding).Render(b.String())) + view.Draw(scr, area) } // renderHeaderDetails renders the details section of the header. diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index 28f1a7230308618628c1261c5a3b67fba7432d17..a77f55fb919cdb7c809d86e698c96265797da5b3 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -141,8 +141,7 @@ type UI struct { // isCanceling tracks whether the user has pressed escape once to cancel. isCanceling bool - // header is the last cached header logo - header string + header *header // sendProgressBar instructs the TUI to send progress bar updates to the // terminal. @@ -261,12 +260,15 @@ func New(com *common.Common) *UI { }, ) + header := newHeader(com) + ui := &UI{ com: com, dialog: dialog.NewOverlay(), keyMap: keyMap, textarea: ta, chat: ch, + header: header, completions: comp, attachments: attachments, todoSpinner: todoSpinner, @@ -325,6 +327,10 @@ func (m *UI) Init() tea.Cmd { // setState changes the UI state and focus. func (m *UI) setState(state uiState, focus uiFocusState) { + if state == uiLanding { + // Always turn off compact mode when going to landing + m.isCompact = false + } m.state = state m.focus = focus // Changing the state may change layout, so update it. @@ -1761,6 +1767,18 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd { return tea.Batch(cmds...) } +// drawHeader draws the header section of the UI. +func (m *UI) drawHeader(scr uv.Screen, area uv.Rectangle) { + m.header.drawHeader( + scr, + area, + m.session, + m.isCompact, + m.detailsOpen, + m.width, + ) +} + // Draw implements [uv.Drawable] and draws the UI model. func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor { layout := m.generateLayout(area.Dx(), area.Dy()) @@ -1775,22 +1793,19 @@ func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor { switch m.state { case uiOnboarding: - header := uv.NewStyledString(m.header) - header.Draw(scr, layout.header) + m.drawHeader(scr, layout.header) // NOTE: Onboarding flow will be rendered as dialogs below, but // positioned at the bottom left of the screen. case uiInitialize: - header := uv.NewStyledString(m.header) - header.Draw(scr, layout.header) + m.drawHeader(scr, layout.header) main := uv.NewStyledString(m.initializeView()) main.Draw(scr, layout.main) case uiLanding: - header := uv.NewStyledString(m.header) - header.Draw(scr, layout.header) + m.drawHeader(scr, layout.header) main := uv.NewStyledString(m.landingView()) main.Draw(scr, layout.main) @@ -1799,8 +1814,7 @@ func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor { case uiChat: if m.isCompact { - header := uv.NewStyledString(m.header) - header.Draw(scr, layout.header) + m.drawHeader(scr, layout.header) } else { m.drawSidebar(scr, layout.sidebar) } @@ -2177,14 +2191,9 @@ func (m *UI) updateSize() { // Handle different app states switch m.state { - case uiOnboarding, uiInitialize, uiLanding: - m.renderHeader(false, m.layout.header.Dx()) - case uiChat: - if m.isCompact { - m.renderHeader(true, m.layout.header.Dx()) - } else { - m.renderSidebarLogo(m.layout.sidebar.Dx()) + if !m.isCompact { + m.cacheSidebarLogo(m.layout.sidebar.Dx()) } } } @@ -2590,18 +2599,8 @@ func (m *UI) renderEditorView(width int) string { ) } -// renderHeader renders and caches the header logo at the specified width. -func (m *UI) renderHeader(compact bool, width int) { - if compact && m.session != nil && m.com.App != nil { - m.header = renderCompactHeader(m.com, m.session, m.com.App.LSPClients, m.detailsOpen, width) - } else { - m.header = renderLogo(m.com.Styles, compact, width) - } -} - -// renderSidebarLogo renders and caches the sidebar logo at the specified -// width. -func (m *UI) renderSidebarLogo(width int) { +// cacheSidebarLogo renders and caches the sidebar logo at the specified width. +func (m *UI) cacheSidebarLogo(width int) { m.sidebarLogo = renderLogo(m.com.Styles, true, width) }