diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go index 091231039c71e24b918a755d56ba0a0de27ae509..8d857ea38463e9d61dc25794e492b33cab0b487b 100644 --- a/internal/tui/components/chat/chat.go +++ b/internal/tui/components/chat/chat.go @@ -107,7 +107,7 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *messageListCmp) View() string { t := styles.CurrentTheme() return t.S().Base. - Padding(1). + Padding(1, 1, 0, 1). Width(m.width). Height(m.height). Render( @@ -508,7 +508,7 @@ func (m *messageListCmp) GetSize() (int, int) { func (m *messageListCmp) SetSize(width int, height int) tea.Cmd { m.width = width m.height = height - return m.listCmp.SetSize(width-2, height-2) // for padding + return m.listCmp.SetSize(width-2, height-1) // for padding } // Blur implements MessageListCmp. diff --git a/internal/tui/components/chat/sidebar/sidebar.go b/internal/tui/components/chat/sidebar/sidebar.go index a01140417955c519b98b5d7f04773f8cb5b58de5..3d9e572b5192354bd97fd6274c482057646ad41c 100644 --- a/internal/tui/components/chat/sidebar/sidebar.go +++ b/internal/tui/components/chat/sidebar/sidebar.go @@ -114,12 +114,22 @@ func (m *sidebarCmp) View() string { t := styles.CurrentTheme() parts := []string{} + style := t.S().Base. + Width(m.width). + Height(m.height). + Padding(1) + if m.compactMode { + style = style.PaddingTop(0) + } + if !m.compactMode { if m.height > LogoHeightBreakpoint { parts = append(parts, m.logo) } else { // Use a smaller logo for smaller screens - parts = append(parts, m.smallerScreenLogo(), "") + parts = append(parts, + logo.SmallRender(m.width-style.GetHorizontalFrameSize()), + "") } } @@ -159,13 +169,6 @@ func (m *sidebarCmp) View() string { ) } - style := t.S().Base. - Width(m.width). - Height(m.height). - Padding(1) - if m.compactMode { - style = style.PaddingTop(0) - } return style.Render( lipgloss.JoinVertical(lipgloss.Left, parts...), ) @@ -934,19 +937,6 @@ func (s *sidebarCmp) currentModelBlock() string { ) } -func (m *sidebarCmp) smallerScreenLogo() string { - t := styles.CurrentTheme() - title := t.S().Base.Foreground(t.Secondary).Render("Charm™") - title += " " + styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary) - remainingWidth := m.width - lipgloss.Width(title) - 3 - if remainingWidth > 0 { - char := "╱" - lines := strings.Repeat(char, remainingWidth) - title += " " + t.S().Base.Foreground(t.Primary).Render(lines) - } - return title -} - // SetSession implements Sidebar. func (m *sidebarCmp) SetSession(session session.Session) tea.Cmd { m.session = session diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index ed5f071a2d68843f89e9f5c92255ce705984e3f1..f7a6dce4baa2c3a2798c30baa6b995f6da72d05b 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/internal/tui/components/chat/splash/splash.go @@ -143,9 +143,11 @@ func (s *splashCmp) Init() tea.Cmd { // SetSize implements SplashPage. func (s *splashCmp) SetSize(width int, height int) tea.Cmd { + wasSmallScreen := s.isSmallScreen() + rerenderLogo := width != s.width s.height = height - if width != s.width { - s.width = width + s.width = width + if rerenderLogo || wasSmallScreen != s.isSmallScreen() { s.logoRendered = s.logoBlock() } // remove padding, logo height, gap, title space @@ -541,9 +543,19 @@ func (s *splashCmp) Cursor() *tea.Cursor { return nil } +func (s *splashCmp) isSmallScreen() bool { + // Consider a screen small if either the width is less than 40 or if the + // height is less than 20 + return s.width < 55 || s.height < 20 +} + func (s *splashCmp) infoSection() string { t := styles.CurrentTheme() - return t.S().Base.PaddingLeft(2).Render( + infoStyle := t.S().Base.PaddingLeft(2) + if s.isSmallScreen() { + infoStyle = infoStyle.MarginTop(1) + } + return infoStyle.Render( lipgloss.JoinVertical( lipgloss.Left, s.cwd(), @@ -556,14 +568,25 @@ func (s *splashCmp) infoSection() string { func (s *splashCmp) logoBlock() string { t := styles.CurrentTheme() - return t.S().Base.Padding(0, 2).Width(s.width).Render( + logoStyle := t.S().Base.Padding(0, 2).Width(s.width) + if s.isSmallScreen() { + // If the width is too small, render a smaller version of the logo + // NOTE: 20 is not correct because [splashCmp.height] is not the + // *actual* window height, instead, it is the height of the splash + // component and that depends on other variables like compact mode and + // the height of the editor. + return logoStyle.Render( + logo.SmallRender(s.width - logoStyle.GetHorizontalFrameSize()), + ) + } + return logoStyle.Render( logo.Render(version.Version, false, logo.Opts{ FieldColor: t.Primary, TitleColorA: t.Secondary, TitleColorB: t.Primary, CharmColor: t.Secondary, VersionColor: t.Primary, - Width: s.width - 4, + Width: s.width - logoStyle.GetHorizontalFrameSize(), }), ) } diff --git a/internal/tui/components/logo/logo.go b/internal/tui/components/logo/logo.go index 195830632f1af0a681bb3075042a76a24d814155..c5902477b944602bd9b70398541631b3362b2e5f 100644 --- a/internal/tui/components/logo/logo.go +++ b/internal/tui/components/logo/logo.go @@ -44,13 +44,19 @@ func Render(version string, compact bool, o Opts) string { // Title. const spacing = 1 - crush := renderWord(spacing, !compact, + letterforms := []letterform{ letterC, letterR, letterU, letterSStylized, letterH, - ) + } + stretchIndex := -1 // -1 means no stretching. + if !compact { + stretchIndex = rand.IntN(len(letterforms)) + } + + crush := renderWord(spacing, stretchIndex, letterforms...) crushWidth := lipgloss.Width(crush) b := new(strings.Builder) for r := range strings.SplitSeq(crush, "\n") { @@ -110,8 +116,23 @@ func Render(version string, compact bool, o Opts) string { return logo } -// renderWord renders letterforms to fork a word. -func renderWord(spacing int, stretchRandomLetter bool, letterforms ...letterform) string { +// SmallRender renders a smaller version of the Crush logo, suitable for +// smaller windows or sidebar usage. +func SmallRender(width int) string { + t := styles.CurrentTheme() + title := t.S().Base.Foreground(t.Secondary).Render("Charm™") + title = fmt.Sprintf("%s %s", title, styles.ApplyBoldForegroundGrad("Crush", t.Secondary, t.Primary)) + remainingWidth := width - lipgloss.Width(title) - 1 // 1 for the space after "Crush" + if remainingWidth > 0 { + lines := strings.Repeat("╱", remainingWidth) + title = fmt.Sprintf("%s %s", title, t.S().Base.Foreground(t.Primary).Render(lines)) + } + return title +} + +// renderWord renders letterforms to fork a word. stretchIndex is the index of +// the letter to stretch, or -1 if no letter should be stretched. +func renderWord(spacing int, stretchIndex int, letterforms ...letterform) string { if spacing < 0 { spacing = 0 } @@ -119,11 +140,6 @@ func renderWord(spacing int, stretchRandomLetter bool, letterforms ...letterform renderedLetterforms := make([]string, len(letterforms)) // pick one letter randomly to stretch - stretchIndex := -1 - if stretchRandomLetter { - stretchIndex = rand.IntN(len(letterforms)) //nolint:gosec - } - for i, letter := range letterforms { renderedLetterforms[i] = letter(i == stretchIndex) }