on main help sign in
chore(ui): use Lip Gloss for color blends
7cbb1dbChristian Rocha
internal/ui/common/elements.go+1−1
@@ -193,7 +193,7 @@ func DialogTitle(t *styles.Styles, title string, width int, fromColor, toColor c
193 remainingWidth := width - length
193 remainingWidth := width - length
194 if remainingWidth > 0 {
194 if remainingWidth > 0 {
195 lines := strings.Repeat(char, remainingWidth)
195 lines := strings.Repeat(char, remainingWidth)
196− lines = styles.ApplyForegroundGrad(t, lines, fromColor, toColor)
196+ lines = styles.ApplyForegroundGrad(t.Base, lines, fromColor, toColor)
197 title = title + " " + lines
197 title = title + " " + lines
198 }
198 }
199 return title
199 return title
internal/ui/logo/example/main.go+22−1
@@ -3,10 +3,13 @@ package main
3import (
3import (
4 "fmt"
4 "fmt"
5 "math/rand/v2"
5 "math/rand/v2"
6+ "os"
6
7
7 "charm.land/lipgloss/v2"
8 "charm.land/lipgloss/v2"
8 "github.com/charmbracelet/crush/internal/ui/logo"
9 "github.com/charmbracelet/crush/internal/ui/logo"
10+ "github.com/charmbracelet/crush/internal/ui/styles"
9 "github.com/charmbracelet/x/exp/slice"
11 "github.com/charmbracelet/x/exp/slice"
12+ "github.com/charmbracelet/x/term"
10)
13)
11
14
12func renderLetterforms(stretch bool) string {
15func renderLetterforms(stretch bool) string {
@@ -42,8 +45,26 @@ func renderLetterforms(stretch bool) string {
42}
45}
43
46
44func main() {
47func main() {
48+ w, _, err := term.GetSize(os.Stdout.Fd())
49+ if err != nil {
50+ fmt.Fprintf(os.Stderr, "Could not get terminal size: %s", err)
51+ }
52+
53+ s := styles.DefaultStyles()
54+ opts := logo.Opts{
55+ FieldColor: s.LogoFieldColor,
56+ TitleColorA: s.LogoTitleColorA,
57+ TitleColorB: s.LogoTitleColorB,
58+ CharmColor: s.LogoCharmColor,
59+ VersionColor: s.LogoVersionColor,
60+ Width: w,
61+ }
62+
63+ lipgloss.Println(logo.Render(s.Base, "v1.0.0", false, opts))
64+ lipgloss.Println(logo.Render(s.Base, "v1.0.0", true, opts))
65+
45 fmt.Println(renderLetterforms(false))
66 fmt.Println(renderLetterforms(false))
46− for range 10 {
67+ for range 5 {
47 fmt.Println(renderLetterforms(true))
68 fmt.Println(renderLetterforms(true))
48 }
69 }
49}
70}
internal/ui/logo/logo.go+5−5
@@ -23,8 +23,9 @@ type Opts struct {
23 TitleColorA color.Color // left gradient ramp point
23 TitleColorA color.Color // left gradient ramp point
24 TitleColorB color.Color // right gradient ramp point
24 TitleColorB color.Color // right gradient ramp point
25 CharmColor color.Color // Charm™ text color
25 CharmColor color.Color // Charm™ text color
26− VersionColor color.Color // Version text color
26+ VersionColor color.Color // version text color
27 Width int // width of the rendered logo, used for truncation
27 Width int // width of the rendered logo, used for truncation
28+ Hyper bool // whether it is Crush or Hypercrush
28}
29}
29
30
30// Render renders the Crush logo. Set the argument to true to render the narrow
31// Render renders the Crush logo. Set the argument to true to render the narrow
@@ -32,7 +33,7 @@ type Opts struct {
32//
33//
33// The compact argument determines whether it renders compact for the sidebar
34// The compact argument determines whether it renders compact for the sidebar
34// or wider for the main pane.
35// or wider for the main pane.
35−func Render(s *styles.Styles, version string, compact bool, o Opts) string {
36+func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
36 const charm = " Charm™"
37 const charm = " Charm™"
37
38
38 fg := func(c color.Color, s string) string {
39 fg := func(c color.Color, s string) string {
@@ -52,12 +53,11 @@ func Render(s *styles.Styles, version string, compact bool, o Opts) string {
52 if !compact {
53 if !compact {
53 stretchIndex = cachedRandN(len(letterforms))
54 stretchIndex = cachedRandN(len(letterforms))
54 }
55 }
55−
56 crush := renderWord(spacing, stretchIndex, letterforms...)
56 crush := renderWord(spacing, stretchIndex, letterforms...)
57 crushWidth := lipgloss.Width(crush)
57 crushWidth := lipgloss.Width(crush)
58 b := new(strings.Builder)
58 b := new(strings.Builder)
59 for r := range strings.SplitSeq(crush, "\n") {
59 for r := range strings.SplitSeq(crush, "\n") {
60− fmt.Fprintln(b, styles.ApplyForegroundGrad(s, r, o.TitleColorA, o.TitleColorB))
60+ fmt.Fprintln(b, styles.ApplyForegroundGrad(base, r, o.TitleColorA, o.TitleColorB))
61 }
61 }
62 crush = b.String()
62 crush = b.String()
63
63
@@ -117,7 +117,7 @@ func Render(s *styles.Styles, version string, compact bool, o Opts) string {
117// smaller windows or sidebar usage.
117// smaller windows or sidebar usage.
118func SmallRender(t *styles.Styles, width int) string {
118func SmallRender(t *styles.Styles, width int) string {
119 title := t.Base.Foreground(t.Secondary).Render("Charm™")
119 title := t.Base.Foreground(t.Secondary).Render("Charm™")
120− title = fmt.Sprintf("%s %s", title, styles.ApplyBoldForegroundGrad(t, "Crush", t.Secondary, t.Primary))
120+ title = fmt.Sprintf("%s %s", title, styles.ApplyBoldForegroundGrad(t.Base, "Crush", t.Secondary, t.Primary))
121 remainingWidth := width - lipgloss.Width(title) - 1 // 1 for the space after "Crush"
121 remainingWidth := width - lipgloss.Width(title) - 1 // 1 for the space after "Crush"
122 if remainingWidth > 0 {
122 if remainingWidth > 0 {
123 lines := strings.Repeat("╱", remainingWidth)
123 lines := strings.Repeat("╱", remainingWidth)
internal/ui/styles/grad.go+9−56
@@ -5,7 +5,7 @@ import (
5 "image/color"
5 "image/color"
6 "strings"
6 "strings"
7
7
8− "github.com/lucasb-eyer/go-colorful"
8+ "charm.land/lipgloss/v2"
9 "github.com/rivo/uniseg"
9 "github.com/rivo/uniseg"
10)
10)
11
11
@@ -13,12 +13,12 @@ import (
13// rendered with a horizontal gradient foreground from color1 to color2. Each
13// rendered with a horizontal gradient foreground from color1 to color2. Each
14// string in the returned slice corresponds to a grapheme cluster in the input
14// string in the returned slice corresponds to a grapheme cluster in the input
15// string. If bold is true, the rendered strings will be bolded.
15// string. If bold is true, the rendered strings will be bolded.
16−func ForegroundGrad(t *Styles, input string, bold bool, color1, color2 color.Color) []string {
16+func ForegroundGrad(base lipgloss.Style, input string, bold bool, color1, color2 color.Color) []string {
17 if input == "" {
17 if input == "" {
18 return []string{""}
18 return []string{""}
19 }
19 }
20 if len(input) == 1 {
20 if len(input) == 1 {
21− style := t.Base.Foreground(color1)
21+ style := base.Foreground(color1)
22 if bold {
22 if bold {
23 style.Bold(true)
23 style.Bold(true)
24 }
24 }
@@ -30,9 +30,9 @@ func ForegroundGrad(t *Styles, input string, bold bool, color1, color2 color.Col
30 clusters = append(clusters, string(gr.Runes()))
30 clusters = append(clusters, string(gr.Runes()))
31 }
31 }
32
32
33− ramp := blendColors(len(clusters), color1, color2)
33+ ramp := lipgloss.Blend1D(len(clusters), color1, color2)
34 for i, c := range ramp {
34 for i, c := range ramp {
35− style := t.Base.Foreground(c)
35+ style := base.Foreground(c)
36 if bold {
36 if bold {
37 style.Bold(true)
37 style.Bold(true)
38 }
38 }
@@ -43,12 +43,12 @@ func ForegroundGrad(t *Styles, input string, bold bool, color1, color2 color.Col
43
43
44// ApplyForegroundGrad renders a given string with a horizontal gradient
44// ApplyForegroundGrad renders a given string with a horizontal gradient
45// foreground.
45// foreground.
46−func ApplyForegroundGrad(t *Styles, input string, color1, color2 color.Color) string {
46+func ApplyForegroundGrad(base lipgloss.Style, input string, color1, color2 color.Color) string {
47 if input == "" {
47 if input == "" {
48 return ""
48 return ""
49 }
49 }
50 var o strings.Builder
50 var o strings.Builder
51− clusters := ForegroundGrad(t, input, false, color1, color2)
51+ clusters := ForegroundGrad(base, input, false, color1, color2)
52 for _, c := range clusters {
52 for _, c := range clusters {
53 fmt.Fprint(&o, c)
53 fmt.Fprint(&o, c)
54 }
54 }
@@ -57,61 +57,14 @@ func ApplyForegroundGrad(t *Styles, input string, color1, color2 color.Color) st
57
57
58// ApplyBoldForegroundGrad renders a given string with a horizontal gradient
58// ApplyBoldForegroundGrad renders a given string with a horizontal gradient
59// foreground.
59// foreground.
60−func ApplyBoldForegroundGrad(t *Styles, input string, color1, color2 color.Color) string {
60+func ApplyBoldForegroundGrad(base lipgloss.Style, input string, color1, color2 color.Color) string {
61 if input == "" {
61 if input == "" {
62 return ""
62 return ""
63 }
63 }
64 var o strings.Builder
64 var o strings.Builder
65− clusters := ForegroundGrad(t, input, true, color1, color2)
65+ clusters := ForegroundGrad(base, input, true, color1, color2)
66 for _, c := range clusters {
66 for _, c := range clusters {
67 fmt.Fprint(&o, c)
67 fmt.Fprint(&o, c)
68 }
68 }
69 return o.String()
69 return o.String()
70}
70}
71−
72−// blendColors returns a slice of colors blended between the given keys.
73−// Blending is done in Hcl to stay in gamut.
74−func blendColors(size int, stops ...color.Color) []color.Color {
75− if len(stops) < 2 {
76− return nil
77− }
78−
79− stopsPrime := make([]colorful.Color, len(stops))
80− for i, k := range stops {
81− stopsPrime[i], _ = colorful.MakeColor(k)
82− }
83−
84− numSegments := len(stopsPrime) - 1
85− blended := make([]color.Color, 0, size)
86−
87− // Calculate how many colors each segment should have.
88− segmentSizes := make([]int, numSegments)
89− baseSize := size / numSegments
90− remainder := size % numSegments
91−
92− // Distribute the remainder across segments.
93− for i := range numSegments {
94− segmentSizes[i] = baseSize
95− if i < remainder {
96− segmentSizes[i]++
97− }
98− }
99−
100− // Generate colors for each segment.
101− for i := range numSegments {
102− c1 := stopsPrime[i]
103− c2 := stopsPrime[i+1]
104− segmentSize := segmentSizes[i]
105−
106− for j := range segmentSize {
107− var t float64
108− if segmentSize > 1 {
109− t = float64(j) / float64(segmentSize-1)
110− }
111− c := c1.BlendHcl(c2, t)
112− blended = append(blended, c)
113− }
114− }
115−
116− return blended
117−}