From 5161cd568f0323e1001def186c15c9186483458a Mon Sep 17 00:00:00 2001 From: Sam Morrison Date: Sun, 30 Oct 2022 19:01:44 -0600 Subject: [PATCH] Add support for gradient colours Adds a struct which implements TerminalColor interface. Added steps to Render for gradient application to text --- color.go | 34 ++++++++++++++++++++++++++ color_test.go | 25 +++++++++++++++++++ style.go | 68 ++++++++++++++++++++++++++++++++++++++------------- style_test.go | 4 +++ 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/color.go b/color.go index d70dfaf3..1125b615 100644 --- a/color.go +++ b/color.go @@ -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() +} diff --git a/color_test.go b/color_test.go index 065b34f8..8ecc6e36 100644 --- a/color_test.go +++ b/color_test.go @@ -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 { diff --git a/style.go b/style.go index 4c149326..eddf5f88 100644 --- a/style.go +++ b/style.go @@ -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) @@ -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) + } } } @@ -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])) } diff --git a/style_test.go b/style_test.go index 825fa7aa..f0276b95 100644 --- a/style_test.go +++ b/style_test.go @@ -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",