From 4f3a308f708ea53e3b27cb92a02e6515107092ff Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Thu, 23 Apr 2026 16:50:28 -0400 Subject: [PATCH] chore(ui): formal hypercrush type treatment --- internal/ui/logo/example/main.go | 57 ++++++++++---------------------- internal/ui/logo/logo.go | 45 +++++++++++++++++++++---- 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/internal/ui/logo/example/main.go b/internal/ui/logo/example/main.go index 4d977d5020c94d32133449afc2edd5bf53e5d197..27637287214af8fb45ac8001359c2e0d0e3d5b70 100644 --- a/internal/ui/logo/example/main.go +++ b/internal/ui/logo/example/main.go @@ -1,49 +1,17 @@ package main +// This is an example for testing logo treatments. Do not remove. + import ( "fmt" - "math/rand/v2" "os" "charm.land/lipgloss/v2" "github.com/charmbracelet/crush/internal/ui/logo" "github.com/charmbracelet/crush/internal/ui/styles" - "github.com/charmbracelet/x/exp/slice" "github.com/charmbracelet/x/term" ) -func renderLetterforms(stretch bool) string { - letterFuncs := []func(bool) string{ - logo.LetterH, - logo.LetterY, - logo.LetterYAlt, - logo.LetterP, - logo.LetterE, - logo.LetterEAlt, - logo.LetterR, - logo.LetterC, - logo.LetterR, - logo.LetterU, - logo.LetterSAlt, - logo.LetterH, - } - - // Which letter to stretch, if we're stretching. - stretchIndex := -1 - if stretch { - stretchIndex = rand.IntN(len(letterFuncs)) - } - - // Build letterforms. - letterforms := make([]string, len(letterFuncs)) - for i, f := range letterFuncs { - letterforms[i] = f(stretch && i == stretchIndex) - } - letterforms = slice.Intersperse(letterforms, " ") - - return lipgloss.JoinHorizontal(lipgloss.Top, letterforms...) -} - func main() { w, _, err := term.GetSize(os.Stdout.Fd()) if err != nil { @@ -58,13 +26,24 @@ func main() { CharmColor: s.LogoCharmColor, VersionColor: s.LogoVersionColor, Width: w, + Unstable: true, + } + + renderCompact := func(hyper bool) string { + opts.Hyper = hyper + return logo.Render(s.Base, "v1.0.0", true, opts) + } + + renderWide := func(hyper bool) string { + opts.Hyper = hyper + return logo.Render(s.Base, "v1.0.0", false, opts) } - lipgloss.Println(logo.Render(s.Base, "v1.0.0", false, opts)) - lipgloss.Println(logo.Render(s.Base, "v1.0.0", true, opts)) + lipgloss.Println( + lipgloss.JoinHorizontal(lipgloss.Top, renderCompact(false), " ", renderCompact(true)), + ) - fmt.Println(renderLetterforms(false)) - for range 5 { - fmt.Println(renderLetterforms(true)) + for i := range 6 { + lipgloss.Println(renderWide(i > 0)) } } diff --git a/internal/ui/logo/logo.go b/internal/ui/logo/logo.go index 847d366f42fe949e778c71eb5d03ef22854e6ff6..c636bdc7c4b03c676e452747e2eca1a4cbce49da 100644 --- a/internal/ui/logo/logo.go +++ b/internal/ui/logo/logo.go @@ -4,6 +4,7 @@ package logo import ( "fmt" "image/color" + "math/rand/v2" "strings" "charm.land/lipgloss/v2" @@ -26,6 +27,11 @@ type Opts struct { VersionColor color.Color // version text color Width int // width of the rendered logo, used for truncation Hyper bool // whether it is Crush or Hypercrush + + // When true, stretch a random letterform on each render. Has no effect in + // compact mode. Mainly for testing. In production you will want to cache + // the stretched letterform to keep the logo from jittering on resize. + Unstable bool } // Render renders the Crush logo. Set the argument to true to render the narrow @@ -34,7 +40,10 @@ type Opts struct { // The compact argument determines whether it renders compact for the sidebar // or wider for the main pane. func Render(base lipgloss.Style, version string, compact bool, o Opts) string { - const charm = " Charm™" + charm := "Charm™" + if !o.Hyper { + charm = " " + charm + } fg := func(c color.Color, s string) string { return lipgloss.NewStyle().Foreground(c).Render(s) @@ -42,18 +51,39 @@ func Render(base lipgloss.Style, version string, compact bool, o Opts) string { // Title. const spacing = 1 - letterforms := []letterform{ + var hyperLetterforms []letterform + if o.Hyper { + hyperLetterforms = []letterform{ + LetterH, + LetterYAlt, + LetterP, + LetterE, + LetterR, + } + } + crushLetterforms := []letterform{ LetterC, LetterR, LetterU, LetterSAlt, LetterH, } + if o.Hyper && !compact { + crushLetterforms = append(hyperLetterforms, crushLetterforms...) + } + stretchIndex := -1 // -1 means no stretching. - if !compact { - stretchIndex = cachedRandN(len(letterforms)) + if !compact && !o.Unstable { + // Always stretch the same letterform, which is picked once at random. + stretchIndex = cachedRandN(len(crushLetterforms)) + } else if !compact && o.Unstable { + // Stretch a random letterform on every render. + stretchIndex = rand.IntN(len(crushLetterforms)) + } + crush := renderWord(spacing, stretchIndex, crushLetterforms...) + if o.Hyper && compact { + crush = renderWord(spacing, stretchIndex, hyperLetterforms...) + "\n" + crush } - crush := renderWord(spacing, stretchIndex, letterforms...) crushWidth := lipgloss.Width(crush) b := new(strings.Builder) for r := range strings.SplitSeq(crush, "\n") { @@ -65,13 +95,16 @@ func Render(base lipgloss.Style, version string, compact bool, o Opts) string { metaRowGap := 1 maxVersionWidth := crushWidth - lipgloss.Width(charm) - metaRowGap version = ansi.Truncate(version, maxVersionWidth, "…") // truncate version if too long. + if o.Hyper && compact { + version += " " + } gap := max(0, crushWidth-lipgloss.Width(charm)-lipgloss.Width(version)) metaRow := fg(o.CharmColor, charm) + strings.Repeat(" ", gap) + fg(o.VersionColor, version) // Join the meta row and big Crush title. crush = strings.TrimSpace(metaRow + "\n" + crush) - // Narrow version. + // Narrow version. If this is Hypercrush, this is also a stacked version. if compact { field := fg(o.FieldColor, strings.Repeat(diag, crushWidth)) return strings.Join([]string{field, field, crush, field, ""}, "\n")