Skip to content

Commit

Permalink
feat: query and set terminal background, foreground, and cursor colors
Browse files Browse the repository at this point in the history
This adds the ability to read and set terminal background, foreground,
and cursor color.
  • Loading branch information
aymanbagabas committed Aug 14, 2024
1 parent 3075646 commit bd99a1c
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 0 deletions.
135 changes: 135 additions & 0 deletions color.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,65 @@ package tea
import (
"fmt"
"image/color"
"math"
"strconv"
"strings"
)

// backgroundColorMsg is a message that requests the terminal background color.
type backgroundColorMsg struct{}

// BackgroundColor is a command that requests the terminal background color.
func BackgroundColor() Msg {
return backgroundColorMsg{}
}

// foregroundColorMsg is a message that requests the terminal foreground color.
type foregroundColorMsg struct{}

// ForegroundColor is a command that requests the terminal foreground color.
func ForegroundColor() Msg {
return foregroundColorMsg{}
}

// cursorColorMsg is a message that requests the terminal cursor color.
type cursorColorMsg struct{}

// CursorColor is a command that requests the terminal cursor color.
func CursorColor() Msg {
return cursorColorMsg{}
}

// setBackgroundColorMsg is a message that sets the terminal background color.
type setBackgroundColorMsg struct{ color.Color }

// SetBackgroundColor is a command that sets the terminal background color.
func SetBackgroundColor(c color.Color) Cmd {
return func() Msg {
return setBackgroundColorMsg{c}
}
}

// setForegroundColorMsg is a message that sets the terminal foreground color.
type setForegroundColorMsg struct{ color.Color }

// SetForegroundColor is a command that sets the terminal foreground color.
func SetForegroundColor(c color.Color) Cmd {
return func() Msg {
return setForegroundColorMsg{c}
}
}

// setCursorColorMsg is a message that sets the terminal cursor color.
type setCursorColorMsg struct{ color.Color }

// SetCursorColor is a command that sets the terminal cursor color.
func SetCursorColor(c color.Color) Cmd {
return func() Msg {
return setCursorColorMsg{c}
}
}

// ForegroundColorMsg represents a foreground color message.
// This message is emitted when the program requests the terminal foreground
// color.
Expand All @@ -17,6 +72,11 @@ func (e ForegroundColorMsg) String() string {
return colorToHex(e)
}

// IsDark returns whether the color is dark.
func (e ForegroundColorMsg) IsDark() bool {
return isDarkColor(e)
}

// BackgroundColorMsg represents a background color message.
// This message is emitted when the program requests the terminal background
// color.
Expand All @@ -27,6 +87,11 @@ func (e BackgroundColorMsg) String() string {
return colorToHex(e)
}

// IsDark returns whether the color is dark.
func (e BackgroundColorMsg) IsDark() bool {
return isDarkColor(e)
}

// CursorColorMsg represents a cursor color change message.
// This message is emitted when the program requests the terminal cursor color.
type CursorColorMsg struct{ color.Color }
Expand All @@ -36,6 +101,11 @@ func (e CursorColorMsg) String() string {
return colorToHex(e)
}

// IsDark returns whether the color is dark.
func (e CursorColorMsg) IsDark() bool {
return isDarkColor(e)
}

type shiftable interface {
~uint | ~uint16 | ~uint32 | ~uint64
}
Expand Down Expand Up @@ -80,3 +150,68 @@ func xParseColor(s string) color.Color {
}
return color.Black
}

func getMaxMin(a, b, c float64) (max, min float64) {

Check failure on line 154 in color.go

View workflow job for this annotation

GitHub Actions / lint

named return max has same name as predeclared identifier (predeclared)

Check failure on line 154 in color.go

View workflow job for this annotation

GitHub Actions / lint

named return max has same name as predeclared identifier (predeclared)
if a > b {
max = a
min = b
} else {
max = b
min = a
}
if c > max {
max = c
} else if c < min {
min = c
}
return max, min
}

func round(x float64) float64 {
return math.Round(x*1000) / 1000

Check failure on line 171 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 1000, in <argument> detected (gomnd)
}

// rgbToHSL converts an RGB triple to an HSL triple.
func rgbToHSL(r, g, b uint8) (h, s, l float64) {
// convert uint32 pre-multiplied value to uint8
// The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
Rnot := float64(r) / 255

Check failure on line 178 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 255, in <operation> detected (gomnd)
Gnot := float64(g) / 255

Check failure on line 179 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 255, in <operation> detected (gomnd)
Bnot := float64(b) / 255

Check failure on line 180 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 255, in <operation> detected (gomnd)
Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
Δ := Cmax - Cmin
// Lightness calculation:
l = (Cmax + Cmin) / 2

Check failure on line 184 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 2, in <operation> detected (gomnd)
// Hue and Saturation Calculation:
if Δ == 0 {
h = 0
s = 0
} else {
switch Cmax {
case Rnot:
h = 60 * (math.Mod((Gnot-Bnot)/Δ, 6))

Check failure on line 192 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 6, in <argument> detected (gomnd)
case Gnot:
h = 60 * (((Bnot - Rnot) / Δ) + 2)

Check failure on line 194 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 60, in <operation> detected (gomnd)
case Bnot:
h = 60 * (((Rnot - Gnot) / Δ) + 4)

Check failure on line 196 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 60, in <operation> detected (gomnd)
}
if h < 0 {
h += 360
}

s = Δ / (1 - math.Abs((2*l)-1))

Check failure on line 202 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 2, in <operation> detected (gomnd)
}

return h, round(s), round(l)
}

// isDarkColor returns whether the given color is dark.
func isDarkColor(c color.Color) bool {
if c == nil {
return true
}

r, g, b, _ := c.RGBA()
_, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8))

Check failure on line 215 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 8, in <argument> detected (gomnd)
return l < 0.5
}
24 changes: 24 additions & 0 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,30 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
p.renderer.execute(ansi.DisableBracketedPaste)
p.bpActive = false

case setBackgroundColorMsg:
if msg.Color != nil {
p.renderer.execute(ansi.SetBackgroundColor(msg.Color))
}

case setForegroundColorMsg:
if msg.Color != nil {
p.renderer.execute(ansi.SetForegroundColor(msg.Color))
}

case setCursorColorMsg:
if msg.Color != nil {
p.renderer.execute(ansi.SetCursorColor(msg.Color))
}

case backgroundColorMsg:
p.renderer.execute(ansi.RequestBackgroundColor)

case foregroundColorMsg:
p.renderer.execute(ansi.RequestForegroundColor)

case cursorColorMsg:
p.renderer.execute(ansi.RequestCursorColor)

case execMsg:
// NB: this blocks.
p.exec(msg.cmd, msg.fn)
Expand Down

0 comments on commit bd99a1c

Please sign in to comment.