Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,17 @@ colors:
text-line: "#00ffff"
text-tag: "#005fff"
background-tag: "#0087ff"
price-color-scheme:
light:
positive-start: "#22C55E"
positive-end: "#15803D"
negative-start: "#EF4444"
negative-end: "#B91C1C"
dark:
positive-start: "#C6FF40"
positive-end: "#779929"
negative-start: "#FF7940"
negative-end: "#994926"
```

* Terminals supporting TrueColor will be able to represent the full color space and in other cases colors will be down sampled
Expand Down
28 changes: 22 additions & 6 deletions internal/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,28 @@ type Config struct {

// ConfigColorScheme represents user defined color scheme
type ConfigColorScheme struct {
Text string `yaml:"text"`
TextLight string `yaml:"text-light"`
TextLabel string `yaml:"text-label"`
TextLine string `yaml:"text-line"`
TextTag string `yaml:"text-tag"`
BackgroundTag string `yaml:"background-tag"`
Text string `yaml:"text"`
TextLight string `yaml:"text-light"`
TextLabel string `yaml:"text-label"`
TextLine string `yaml:"text-line"`
TextTag string `yaml:"text-tag"`
BackgroundTag string `yaml:"background-tag"`
PriceColorScheme ConfigPriceColorSchemeGroup `yaml:"price-color-scheme"`
}

// ConfigPriceColorSchemeGroup represents user defined price color scheme for light and dark mode
type ConfigPriceColorSchemeGroup struct {
Light ConfigPriceColorScheme `yaml:"light"`
Dark ConfigPriceColorScheme `yaml:"dark"`
}

// ConfigPriceColorScheme represents user defined price color scheme with start and end colors
// for positive and negative price changes
type ConfigPriceColorScheme struct {
PositiveStart string `yaml:"positive-start"`
PositiveEnd string `yaml:"positive-end"`
NegativeStart string `yaml:"negative-start"`
NegativeEnd string `yaml:"negative-end"`
}

type ConfigAssetGroup struct {
Expand Down
133 changes: 87 additions & 46 deletions internal/ui/util/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,40 @@ import (

const (
maxPercentChangeColorGradient = 10
strongChangeThreshold = 10.0
mediumChangeThreshold = 5.0
)

//nolint:gochecknoglobals
var (
p = te.ColorProfile()
stylePricePositive = newStyleFromGradient("#C6FF40", "#779929")
stylePriceNegative = newStyleFromGradient("#FF7940", "#994926")
p = te.ColorProfile()
colorRegex = regexp.MustCompile(`^#(?:[0-9a-fA-F]{3}){1,2}$`)
)

//nolint:gochecknoglobals
var defaultColorScheme = c.ConfigColorScheme{
Text: "#d0d0d0",
TextLight: "#8a8a8a",
TextLabel: "#626262",
TextLine: "#3a3a3a",
BackgroundTag: "#303030",
TextTag: "#8a8a8a",
PriceColorScheme: c.ConfigPriceColorSchemeGroup{
Light: c.ConfigPriceColorScheme{
PositiveStart: "#22C55E",
PositiveEnd: "#15803D",
NegativeStart: "#EF4444",
NegativeEnd: "#B91C1C",
},
Dark: c.ConfigPriceColorScheme{
PositiveStart: "#C6FF40",
PositiveEnd: "#779929",
NegativeStart: "#FF7940",
NegativeEnd: "#994926",
},
},
}

// NewStyle creates a new predefined style function
func NewStyle(fg string, bg string, bold bool) func(string) string {
s := te.Style{}.Foreground(p.Color(fg)).Background(p.Color(bg))
Expand All @@ -30,115 +55,131 @@ func NewStyle(fg string, bg string, bold bool) func(string) string {
return s.Styled
}

func stylePrice(percent float64, text string) string { //nolint:cyclop
func getStylePriceFn(colorScheme c.ConfigPriceColorScheme) func(float64, string) string {
positiveGradient := newStyleFromGradient(colorScheme.PositiveStart, colorScheme.PositiveEnd)
negativeGradient := newStyleFromGradient(colorScheme.NegativeStart, colorScheme.NegativeEnd)

out := te.String(text)
return func(percent float64, text string) string { //nolint:cyclop
if p == te.TrueColor && percent > 0.0 {
return positiveGradient(percent, text)
}

if percent == 0.0 {
return out.Foreground(p.Color("241")).String()
}

if p == te.TrueColor && percent > 0.0 {
return stylePricePositive(percent, text)
}
if p == te.TrueColor && percent < 0.0 {
return negativeGradient(percent, text)
}

if p == te.TrueColor && percent < 0.0 {
return stylePriceNegative(percent, text)
}
out := te.String(text)

if percent > 10.0 {
return out.Foreground(p.Color("70")).String()
}
if percent == 0.0 {
return out.Foreground(p.Color("241")).String()
}

if percent > 5 {
return out.Foreground(p.Color("76")).String()
}
if percent > strongChangeThreshold {
return out.Foreground(p.Color("70")).String()
}

if percent > 0.0 {
return out.Foreground(p.Color("82")).String()
}
if percent > mediumChangeThreshold {
return out.Foreground(p.Color("76")).String()
}

if percent < -10.0 {
return out.Foreground(p.Color("124")).String()
}
if percent > 0.0 {
return out.Foreground(p.Color("82")).String()
}

if percent < -5.0 {
return out.Foreground(p.Color("160")).String()
}
if percent < strongChangeThreshold*-1 {
return out.Foreground(p.Color("124")).String()
}

return out.Foreground(p.Color("196")).String()
if percent < mediumChangeThreshold*-1 {
return out.Foreground(p.Color("160")).String()
}

return out.Foreground(p.Color("196")).String()
}
}

func newStyleFromGradient(startColorHex string, endColorHex string) func(float64, string) string {
c1, _ := colorful.Hex(startColorHex)
c2, _ := colorful.Hex(endColorHex)

return func(percent float64, text string) string {

normalizedPercent := getNormalizedPercentWithMax(percent, maxPercentChangeColorGradient)
textColor := p.Color(c1.BlendHsv(c2, normalizedPercent).Hex())

return te.String(text).Foreground(textColor).String()

}
}

// Normalize 0-100 percent with a maximum percent value
func getNormalizedPercentWithMax(percent float64, maxPercent float64) float64 {

absolutePercent := math.Abs(percent)
if absolutePercent >= maxPercent {
return 1.0
}

return math.Abs(percent / maxPercent)

}

// GetColorScheme generates a color scheme based on user defined colors or defaults
func GetColorScheme(colorScheme c.ConfigColorScheme) c.Styles {
priceColorScheme := getPriceColorScheme(colorScheme)

return c.Styles{
Text: NewStyle(
getColorOrDefault(colorScheme.Text, "#d0d0d0"),
getColorOrDefault(colorScheme.Text, defaultColorScheme.Text),
"",
false,
),
TextLight: NewStyle(
getColorOrDefault(colorScheme.TextLight, "#8a8a8a"),
getColorOrDefault(colorScheme.TextLight, defaultColorScheme.TextLight),
"",
false,
),
TextBold: NewStyle(
getColorOrDefault(colorScheme.Text, "#d0d0d0"),
getColorOrDefault(colorScheme.Text, defaultColorScheme.Text),
"",
true,
),
TextLabel: NewStyle(
getColorOrDefault(colorScheme.TextLabel, "#626262"),
getColorOrDefault(colorScheme.TextLabel, defaultColorScheme.TextLabel),
"",
false,
),
TextLine: NewStyle(
getColorOrDefault(colorScheme.TextLine, "#3a3a3a"),
getColorOrDefault(colorScheme.TextLine, defaultColorScheme.TextLine),
"",
false,
),
TextPrice: stylePrice,
TextPrice: getStylePriceFn(priceColorScheme),
Tag: NewStyle(
getColorOrDefault(colorScheme.TextTag, "#8a8a8a"),
getColorOrDefault(colorScheme.BackgroundTag, "#303030"),
getColorOrDefault(colorScheme.TextTag, defaultColorScheme.TextTag),
getColorOrDefault(colorScheme.BackgroundTag, defaultColorScheme.BackgroundTag),
false,
),
}
}

// getPriceColorScheme returns the color scheme group for price changes based
// on user defined colors or defaults for light and dark mode
func getPriceColorScheme(colorScheme c.ConfigColorScheme) c.ConfigPriceColorScheme {
source := colorScheme.PriceColorScheme.Light
defaults := defaultColorScheme.PriceColorScheme.Light

if te.HasDarkBackground() {
source = colorScheme.PriceColorScheme.Dark
defaults = defaultColorScheme.PriceColorScheme.Dark
}

return c.ConfigPriceColorScheme{
PositiveStart: getColorOrDefault(source.PositiveStart, defaults.PositiveStart),
PositiveEnd: getColorOrDefault(source.PositiveEnd, defaults.PositiveEnd),
NegativeStart: getColorOrDefault(source.NegativeStart, defaults.NegativeStart),
NegativeEnd: getColorOrDefault(source.NegativeEnd, defaults.NegativeEnd),
}
}

func getColorOrDefault(colorConfig string, colorDefault string) string {
re := regexp.MustCompile(`^#(?:[0-9a-fA-F]{3}){1,2}$`)

if len(re.FindStringIndex(colorConfig)) > 0 {
if len(colorRegex.FindStringIndex(colorConfig)) > 0 {
return colorConfig
}

Expand Down