Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for gradient colours #154

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 34 additions & 0 deletions color.go
Expand Up @@ -236,3 +236,37 @@ func (cac CompleteAdaptiveColor) color() termenv.Color {
func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) {
return termenv.ConvertToRGB(cac.color()).RGBA()
}

// GradientColour specifies the start and end hex values for a colour gradient.
// The RGBA is blended based on the position/steps parameters. During render the
// gradient will be applied to the string provided, and the Steps parameter
// will be set to the Width() of the string provided to render.
// Currently only right to left gradient is supported.
//
// TODO: Add option for multiline:
// - corner to corner
// - radial
// - inverse
type GradientColour struct {
Start string
End string
Steps int
Position int
}

func (gc GradientColour) value() string {
sc := termenv.ConvertToRGB(ColorProfile().Color(gc.Start))
ec := termenv.ConvertToRGB(ColorProfile().Color(gc.End))

n := sc.BlendRgb(ec, float64(gc.Position)/float64(gc.Steps))

return n.Hex()
}

func (gc GradientColour) color() termenv.Color {
return ColorProfile().Color(gc.value())
}

func (gc GradientColour) RGBA() (r, g, b, a uint32) {
return termenv.ConvertToRGB(gc.color()).RGBA()
}
25 changes: 25 additions & 0 deletions color_test.go
Expand Up @@ -213,6 +213,31 @@ func TestRGBA(t *testing.T) {
},
0xFF0000,
},
// lipgloss.GradientColour
{
termenv.TrueColor,
false,
GradientColour{Start: "#FF0000", End: "#0000FF", Steps: 10, Position: 0},
0xFF0000,
},
{
termenv.TrueColor,
true,
GradientColour{Start: "#FF0000", End: "#0000FF", Steps: 10, Position: 10},
0x0000FF,
},
{
termenv.TrueColor,
false,
GradientColour{Start: "#FF00FF", End: "#00FF00", Steps: 10, Position: 5},
0x808080,
},
{
termenv.TrueColor,
false,
GradientColour{Start: "#FF00FF", End: "#00FF00", Steps: 10, Position: 2},
0xCC33CC,
},
}

for i, tc := range tt {
Expand Down
68 changes: 51 additions & 17 deletions style.go
Expand Up @@ -167,8 +167,10 @@ func (s Style) Render(str string) string {
blink = s.getAsBool(blinkKey, false)
faint = s.getAsBool(faintKey, false)

fg = s.getAsColor(foregroundKey)
bg = s.getAsColor(backgroundKey)
fg = s.getAsColor(foregroundKey)
fggradient, fggrad = fg.(GradientColour)
bg = s.getAsColor(backgroundKey)
bggradient, bggrad = bg.(GradientColour)

width = s.getAsInt(widthKey)
height = s.getAsInt(heightKey)
Expand Down Expand Up @@ -227,24 +229,34 @@ func (s Style) Render(str string) string {
}

if fg != noColor {
fgc := fg.color()
te = te.Foreground(fgc)
if styleWhitespace {
teWhitespace = teWhitespace.Foreground(fgc)
}
if useSpaceStyler {
teSpace = teSpace.Foreground(fgc)
if fggrad {
fggradient.Position = 0
fggradient.Steps = Width(str)
} else {
fgc := fg.color()
te = te.Foreground(fgc)
if styleWhitespace {
teWhitespace = teWhitespace.Foreground(fgc)
}
if useSpaceStyler {
teSpace = teSpace.Foreground(fgc)
}
}
}

if bg != noColor {
bgc := bg.color()
te = te.Background(bgc)
if colorWhitespace {
teWhitespace = teWhitespace.Background(bgc)
}
if useSpaceStyler {
teSpace = teSpace.Background(bgc)
if bggrad {
bggradient.Position = 0
bggradient.Steps = Width(str)
} else {
bgc := bg.color()
te = te.Background(bgc)
if colorWhitespace {
teWhitespace = teWhitespace.Background(bgc)
}
if useSpaceStyler {
teSpace = teSpace.Background(bgc)
}
}
}

Expand Down Expand Up @@ -280,15 +292,37 @@ func (s Style) Render(str string) string {

l := strings.Split(str, "\n")
for i := range l {
if useSpaceStyler {
var fte, fteSpace termenv.Style
if i == 0 {
fte, fteSpace = te, teSpace
}
if useSpaceStyler || fggrad || bggrad {
// Look for spaces and apply a different styler
for _, r := range l[i] {
if fggrad {
te = fte.Foreground(fggradient.color())
teSpace = fteSpace.Foreground(fggradient.color())
fggradient.Position++
}
if bggrad {
te = fte.Background(bggradient.color())
teSpace = fteSpace.Background(bggradient.color())
bggradient.Position++
}

if unicode.IsSpace(r) {
b.WriteString(teSpace.Styled(string(r)))
continue
}
b.WriteString(te.Styled(string(r)))
}

if fggrad {
fggradient.Position = 0
}
if bggrad {
bggradient.Position = 0
}
} else {
b.WriteString(te.Styled(l[i]))
}
Expand Down
4 changes: 4 additions & 0 deletions style_test.go
Expand Up @@ -18,6 +18,10 @@ func TestStyleRender(t *testing.T) {
NewStyle().Foreground(Color("#5A56E0")),
"\x1b[38;2;89;86;224mhello\x1b[0m",
},
{
NewStyle().Background(GradientColour{Start: "#00FF00", End: "#FF00FF"}),
"\x1b[48;2;0;255;0mh\x1b[0m\x1b[48;2;51;204;51me\x1b[0m\x1b[48;2;102;153;102ml\x1b[0m\x1b[48;2;153;102;153ml\x1b[0m\x1b[48;2;204;51;204mo\x1b[0m",
},
{
NewStyle().Bold(true),
"\x1b[1mhello\x1b[0m",
Expand Down