on main help sign in
chore(ui): formal hypercrush type treatment
4f3a308Christian Rocha
internal/ui/logo/example/main.go+18−39
@@ -1,49 +1,17 @@
1package main
1package main
2
2
3+// This is an example for testing logo treatments. Do not remove.
4+
3import (
5import (
4 "fmt"
6 "fmt"
5− "math/rand/v2"
6 "os"
7 "os"
7
8
8 "charm.land/lipgloss/v2"
9 "charm.land/lipgloss/v2"
9 "github.com/charmbracelet/crush/internal/ui/logo"
10 "github.com/charmbracelet/crush/internal/ui/logo"
10 "github.com/charmbracelet/crush/internal/ui/styles"
11 "github.com/charmbracelet/crush/internal/ui/styles"
11− "github.com/charmbracelet/x/exp/slice"
12 "github.com/charmbracelet/x/term"
12 "github.com/charmbracelet/x/term"
13)
13)
14
14
15−func renderLetterforms(stretch bool) string {
16− letterFuncs := []func(bool) string{
17− logo.LetterH,
18− logo.LetterY,
19− logo.LetterYAlt,
20− logo.LetterP,
21− logo.LetterE,
22− logo.LetterEAlt,
23− logo.LetterR,
24− logo.LetterC,
25− logo.LetterR,
26− logo.LetterU,
27− logo.LetterSAlt,
28− logo.LetterH,
29− }
30−
31− // Which letter to stretch, if we're stretching.
32− stretchIndex := -1
33− if stretch {
34− stretchIndex = rand.IntN(len(letterFuncs))
35− }
36−
37− // Build letterforms.
38− letterforms := make([]string, len(letterFuncs))
39− for i, f := range letterFuncs {
40− letterforms[i] = f(stretch && i == stretchIndex)
41− }
42− letterforms = slice.Intersperse(letterforms, " ")
43−
44− return lipgloss.JoinHorizontal(lipgloss.Top, letterforms...)
45−}
46−
47func main() {
15func main() {
48 w, _, err := term.GetSize(os.Stdout.Fd())
16 w, _, err := term.GetSize(os.Stdout.Fd())
49 if err != nil {
17 if err != nil {
@@ -58,13 +26,24 @@ func main() {
58 CharmColor: s.LogoCharmColor,
26 CharmColor: s.LogoCharmColor,
59 VersionColor: s.LogoVersionColor,
27 VersionColor: s.LogoVersionColor,
60 Width: w,
28 Width: w,
29+ Unstable: true,
30+ }
31+
32+ renderCompact := func(hyper bool) string {
33+ opts.Hyper = hyper
34+ return logo.Render(s.Base, "v1.0.0", true, opts)
35+ }
36+
37+ renderWide := func(hyper bool) string {
38+ opts.Hyper = hyper
39+ return logo.Render(s.Base, "v1.0.0", false, opts)
61 }
40 }
62
41
63− lipgloss.Println(logo.Render(s.Base, "v1.0.0", false, opts))
42+ lipgloss.Println(
64− lipgloss.Println(logo.Render(s.Base, "v1.0.0", true, opts))
43+ lipgloss.JoinHorizontal(lipgloss.Top, renderCompact(false), " ", renderCompact(true)),
44+ )
65
45
66− fmt.Println(renderLetterforms(false))
67− for range 5 {
46+ for i := range 6 {
68− fmt.Println(renderLetterforms(true))
47+ lipgloss.Println(renderWide(i > 0))
69 }
48 }
70}
49}
internal/ui/logo/logo.go+39−6
@@ -4,6 +4,7 @@ package logo
4import (
4import (
5 "fmt"
5 "fmt"
6 "image/color"
6 "image/color"
7+ "math/rand/v2"
7 "strings"
8 "strings"
8
9
9 "charm.land/lipgloss/v2"
10 "charm.land/lipgloss/v2"
@@ -26,6 +27,11 @@ type Opts struct {
26 VersionColor color.Color // version text color
27 VersionColor color.Color // version text color
27 Width int // width of the rendered logo, used for truncation
28 Width int // width of the rendered logo, used for truncation
28 Hyper bool // whether it is Crush or Hypercrush
29 Hyper bool // whether it is Crush or Hypercrush
30+
31+ // When true, stretch a random letterform on each render. Has no effect in
32+ // compact mode. Mainly for testing. In production you will want to cache
33+ // the stretched letterform to keep the logo from jittering on resize.
34+ Unstable bool
29}
35}
30
36
31// Render renders the Crush logo. Set the argument to true to render the narrow
37// Render renders the Crush logo. Set the argument to true to render the narrow
@@ -34,7 +40,10 @@ type Opts struct {
34// The compact argument determines whether it renders compact for the sidebar
40// The compact argument determines whether it renders compact for the sidebar
35// or wider for the main pane.
41// or wider for the main pane.
36func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
42func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
37− const charm = " Charm™"
43+ charm := "Charm™"
44+ if !o.Hyper {
45+ charm = " " + charm
46+ }
38
47
39 fg := func(c color.Color, s string) string {
48 fg := func(c color.Color, s string) string {
40 return lipgloss.NewStyle().Foreground(c).Render(s)
49 return lipgloss.NewStyle().Foreground(c).Render(s)
@@ -42,18 +51,39 @@ func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
42
51
43 // Title.
52 // Title.
44 const spacing = 1
53 const spacing = 1
54+ var hyperLetterforms []letterform
55+ if o.Hyper {
56+ hyperLetterforms = []letterform{
57+ LetterH,
58+ LetterYAlt,
59+ LetterP,
60+ LetterE,
61+ LetterR,
62+ }
63+ }
45− letterforms := []letterform{
64+ crushLetterforms := []letterform{
46 LetterC,
65 LetterC,
47 LetterR,
66 LetterR,
48 LetterU,
67 LetterU,
49 LetterSAlt,
68 LetterSAlt,
50 LetterH,
69 LetterH,
51 }
70 }
71+ if o.Hyper && !compact {
72+ crushLetterforms = append(hyperLetterforms, crushLetterforms...)
73+ }
74+
52 stretchIndex := -1 // -1 means no stretching.
75 stretchIndex := -1 // -1 means no stretching.
53− if !compact {
76+ if !compact && !o.Unstable {
77+ // Always stretch the same letterform, which is picked once at random.
54− stretchIndex = cachedRandN(len(letterforms))
78+ stretchIndex = cachedRandN(len(crushLetterforms))
79+ } else if !compact && o.Unstable {
80+ // Stretch a random letterform on every render.
81+ stretchIndex = rand.IntN(len(crushLetterforms))
82+ }
83+ crush := renderWord(spacing, stretchIndex, crushLetterforms...)
84+ if o.Hyper && compact {
85+ crush = renderWord(spacing, stretchIndex, hyperLetterforms...) + "\n" + crush
55 }
86 }
56− crush := renderWord(spacing, stretchIndex, letterforms...)
57 crushWidth := lipgloss.Width(crush)
87 crushWidth := lipgloss.Width(crush)
58 b := new(strings.Builder)
88 b := new(strings.Builder)
59 for r := range strings.SplitSeq(crush, "\n") {
89 for r := range strings.SplitSeq(crush, "\n") {
@@ -65,13 +95,16 @@ func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
65 metaRowGap := 1
95 metaRowGap := 1
66 maxVersionWidth := crushWidth - lipgloss.Width(charm) - metaRowGap
96 maxVersionWidth := crushWidth - lipgloss.Width(charm) - metaRowGap
67 version = ansi.Truncate(version, maxVersionWidth, "…") // truncate version if too long.
97 version = ansi.Truncate(version, maxVersionWidth, "…") // truncate version if too long.
98+ if o.Hyper && compact {
99+ version += " "
100+ }
68 gap := max(0, crushWidth-lipgloss.Width(charm)-lipgloss.Width(version))
101 gap := max(0, crushWidth-lipgloss.Width(charm)-lipgloss.Width(version))
69 metaRow := fg(o.CharmColor, charm) + strings.Repeat(" ", gap) + fg(o.VersionColor, version)
102 metaRow := fg(o.CharmColor, charm) + strings.Repeat(" ", gap) + fg(o.VersionColor, version)
70
103
71 // Join the meta row and big Crush title.
104 // Join the meta row and big Crush title.
72 crush = strings.TrimSpace(metaRow + "\n" + crush)
105 crush = strings.TrimSpace(metaRow + "\n" + crush)
73
106
74− // Narrow version.
107+ // Narrow version. If this is Hypercrush, this is also a stacked version.
75 if compact {
108 if compact {
76 field := fg(o.FieldColor, strings.Repeat(diag, crushWidth))
109 field := fg(o.FieldColor, strings.Repeat(diag, crushWidth))
77 return strings.Join([]string{field, field, crush, field, ""}, "\n")
110 return strings.Join([]string{field, field, crush, field, ""}, "\n")