mirror of
https://github.com/gdamore/tcell.git
synced 2025-04-24 13:48:51 +08:00
Colored underlines.
This supports UNIX and Windows.
This commit is contained in:
parent
ed7527708c
commit
826c271964
@ -171,6 +171,18 @@ func main() {
|
|||||||
puts(s, style, 2, row, "Dashed Underline")
|
puts(s, style, 2, row, "Dashed Underline")
|
||||||
row++
|
row++
|
||||||
|
|
||||||
|
style = plain.Underline(true).UnderlineColor(tcell.ColorBlue)
|
||||||
|
puts(s, style, 2, row, "Blue Underline")
|
||||||
|
row++
|
||||||
|
|
||||||
|
style = plain.Underline(true).UnderlineColor(tcell.ColorHoneydew)
|
||||||
|
puts(s, style, 2, row, "Honeydew Underline")
|
||||||
|
row++
|
||||||
|
|
||||||
|
style = plain.CurlyUnderline(true).UnderlineColor(tcell.NewRGBColor(0xc5, 0x8a, 0xf9))
|
||||||
|
puts(s, style, 2, row, "Pink Curly Underline")
|
||||||
|
row++
|
||||||
|
|
||||||
style = plain.Url("http://github.com/gdamore/tcell")
|
style = plain.Url("http://github.com/gdamore/tcell")
|
||||||
puts(s, style, 2, row, "HyperLink")
|
puts(s, style, 2, row, "HyperLink")
|
||||||
row++
|
row++
|
||||||
|
@ -168,6 +168,9 @@ const (
|
|||||||
vtCurlyUnderline = "\x1b[4:3m"
|
vtCurlyUnderline = "\x1b[4:3m"
|
||||||
vtDottedUnderline = "\x1b[4:4m"
|
vtDottedUnderline = "\x1b[4:4m"
|
||||||
vtDashedUnderline = "\x1b[4:5m"
|
vtDashedUnderline = "\x1b[4:5m"
|
||||||
|
vtUnderColor = "\x1b[58:5:%dm"
|
||||||
|
vtUnderColorRGB = "\x1b[58:2::%d:%d:%dm"
|
||||||
|
vtUnderColorReset = "\x1b[59m"
|
||||||
)
|
)
|
||||||
|
|
||||||
var vtCursorStyles = map[CursorStyle]string{
|
var vtCursorStyles = map[CursorStyle]string{
|
||||||
@ -879,7 +882,7 @@ func mapColor2RGB(c Color) uint16 {
|
|||||||
|
|
||||||
// Map a tcell style to Windows attributes
|
// Map a tcell style to Windows attributes
|
||||||
func (s *cScreen) mapStyle(style Style) uint16 {
|
func (s *cScreen) mapStyle(style Style) uint16 {
|
||||||
f, b, a := style.Decompose()
|
f, b, a := style.fg, style.bg, style.attrs
|
||||||
fa := s.oscreen.attrs & 0xf
|
fa := s.oscreen.attrs & 0xf
|
||||||
ba := (s.oscreen.attrs) >> 4 & 0xf
|
ba := (s.oscreen.attrs) >> 4 & 0xf
|
||||||
if f != ColorDefault && f != ColorReset {
|
if f != ColorDefault && f != ColorReset {
|
||||||
@ -916,7 +919,7 @@ func (s *cScreen) mapStyle(style Style) uint16 {
|
|||||||
func (s *cScreen) sendVtStyle(style Style) {
|
func (s *cScreen) sendVtStyle(style Style) {
|
||||||
esc := &strings.Builder{}
|
esc := &strings.Builder{}
|
||||||
|
|
||||||
fg, bg, attrs := style.Decompose()
|
fg, bg, uc, attrs := style.fg, style.bg, style.under, style.attrs
|
||||||
|
|
||||||
esc.WriteString(vtSgr0)
|
esc.WriteString(vtSgr0)
|
||||||
|
|
||||||
@ -927,6 +930,17 @@ func (s *cScreen) sendVtStyle(style Style) {
|
|||||||
esc.WriteString(vtBlink)
|
esc.WriteString(vtBlink)
|
||||||
}
|
}
|
||||||
if attrs&(AttrUnderline|AttrDoubleUnderline|AttrCurlyUnderline|AttrDottedUnderline|AttrDashedUnderline) != 0 {
|
if attrs&(AttrUnderline|AttrDoubleUnderline|AttrCurlyUnderline|AttrDottedUnderline|AttrDashedUnderline) != 0 {
|
||||||
|
if uc.Valid() {
|
||||||
|
if uc == ColorReset {
|
||||||
|
esc.WriteString(vtUnderColorReset)
|
||||||
|
} else if uc.IsRGB() {
|
||||||
|
r, g, b := uc.RGB()
|
||||||
|
_, _ = fmt.Fprintf(esc, vtUnderColorRGB, int(r), int(g), int(b))
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintf(esc, vtUnderColor, uc&0xff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
esc.WriteString(vtUnderline)
|
esc.WriteString(vtUnderline)
|
||||||
// legacy ConHost does not understand these but Terminal does
|
// legacy ConHost does not understand these but Terminal does
|
||||||
if (attrs & AttrDoubleUnderline) != 0 {
|
if (attrs & AttrDoubleUnderline) != 0 {
|
||||||
|
78
style.go
78
style.go
@ -25,6 +25,7 @@ package tcell
|
|||||||
type Style struct {
|
type Style struct {
|
||||||
fg Color
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
|
under Color
|
||||||
attrs AttrMask
|
attrs AttrMask
|
||||||
url string
|
url string
|
||||||
urlId string
|
urlId string
|
||||||
@ -40,50 +41,35 @@ var styleInvalid = Style{attrs: AttrInvalid}
|
|||||||
// Foreground returns a new style based on s, with the foreground color set
|
// Foreground returns a new style based on s, with the foreground color set
|
||||||
// as requested. ColorDefault can be used to select the global default.
|
// as requested. ColorDefault can be used to select the global default.
|
||||||
func (s Style) Foreground(c Color) Style {
|
func (s Style) Foreground(c Color) Style {
|
||||||
return Style{
|
s2 := s
|
||||||
fg: c,
|
s2.fg = c
|
||||||
bg: s.bg,
|
return s2
|
||||||
attrs: s.attrs,
|
|
||||||
url: s.url,
|
|
||||||
urlId: s.urlId,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background returns a new style based on s, with the background color set
|
// Background returns a new style based on s, with the background color set
|
||||||
// as requested. ColorDefault can be used to select the global default.
|
// as requested. ColorDefault can be used to select the global default.
|
||||||
func (s Style) Background(c Color) Style {
|
func (s Style) Background(c Color) Style {
|
||||||
return Style{
|
s2 := s
|
||||||
fg: s.fg,
|
s2.bg = c
|
||||||
bg: c,
|
return s2
|
||||||
attrs: s.attrs,
|
|
||||||
url: s.url,
|
|
||||||
urlId: s.urlId,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompose breaks a style up, returning the foreground, background,
|
// Decompose breaks a style up, returning the foreground, background,
|
||||||
// and other attributes. The URL if set is not included.
|
// and other attributes. The URL if set is not included.
|
||||||
|
// Deprecated: Applications should not attempt to decompose style,
|
||||||
|
// as this content is not sufficient to describe the actual style.
|
||||||
func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) {
|
func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) {
|
||||||
return s.fg, s.bg, s.attrs
|
return s.fg, s.bg, s.attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Style) setAttrs(attrs AttrMask, on bool) Style {
|
func (s Style) setAttrs(attrs AttrMask, on bool) Style {
|
||||||
|
s2 := s
|
||||||
if on {
|
if on {
|
||||||
return Style{
|
s2.attrs |= attrs
|
||||||
fg: s.fg,
|
} else {
|
||||||
bg: s.bg,
|
s2.attrs &^= attrs
|
||||||
attrs: s.attrs | attrs,
|
|
||||||
url: s.url,
|
|
||||||
urlId: s.urlId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Style{
|
|
||||||
fg: s.fg,
|
|
||||||
bg: s.bg,
|
|
||||||
attrs: s.attrs &^ attrs,
|
|
||||||
url: s.url,
|
|
||||||
urlId: s.urlId,
|
|
||||||
}
|
}
|
||||||
|
return s2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal returns the style with all attributes disabled.
|
// Normal returns the style with all attributes disabled.
|
||||||
@ -152,29 +138,27 @@ func (s Style) DashedUnderline(on bool) Style {
|
|||||||
return s.setAttrs(AttrDashedUnderline, on)
|
return s.setAttrs(AttrDashedUnderline, on)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Style) UnderlineColor(c Color) Style {
|
||||||
|
s2 := s
|
||||||
|
s2.under = c
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
|
||||||
// Attributes returns a new style based on s, with its attributes set as
|
// Attributes returns a new style based on s, with its attributes set as
|
||||||
// specified.
|
// specified.
|
||||||
func (s Style) Attributes(attrs AttrMask) Style {
|
func (s Style) Attributes(attrs AttrMask) Style {
|
||||||
return Style{
|
s2 := s
|
||||||
fg: s.fg,
|
s2.attrs = attrs
|
||||||
bg: s.bg,
|
return s2
|
||||||
attrs: attrs,
|
|
||||||
url: s.url,
|
|
||||||
urlId: s.urlId,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Url returns a style with the Url set. If the provided Url is not empty,
|
// Url returns a style with the Url set. If the provided Url is not empty,
|
||||||
// and the terminal supports it, text will typically be marked up as a clickable
|
// and the terminal supports it, text will typically be marked up as a clickable
|
||||||
// link to that Url. If the Url is empty, then this mode is turned off.
|
// link to that Url. If the Url is empty, then this mode is turned off.
|
||||||
func (s Style) Url(url string) Style {
|
func (s Style) Url(url string) Style {
|
||||||
return Style{
|
s2 := s
|
||||||
fg: s.fg,
|
s2.url = url
|
||||||
bg: s.bg,
|
return s2
|
||||||
attrs: s.attrs,
|
|
||||||
url: url,
|
|
||||||
urlId: s.urlId,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UrlId returns a style with the UrlId set. If the provided UrlId is not empty,
|
// UrlId returns a style with the UrlId set. If the provided UrlId is not empty,
|
||||||
@ -182,11 +166,7 @@ func (s Style) Url(url string) Style {
|
|||||||
// terminal supports it, any text with the same UrlId will be grouped as if it
|
// terminal supports it, any text with the same UrlId will be grouped as if it
|
||||||
// were one Url, even if it spans multiple lines.
|
// were one Url, even if it spans multiple lines.
|
||||||
func (s Style) UrlId(id string) Style {
|
func (s Style) UrlId(id string) Style {
|
||||||
return Style{
|
s2 := s
|
||||||
fg: s.fg,
|
s2.urlId = "id=" + id
|
||||||
bg: s.bg,
|
return s2
|
||||||
attrs: s.attrs,
|
|
||||||
url: s.url,
|
|
||||||
urlId: "id=" + id,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 The TCell Authors
|
// Copyright 2024 The TCell Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@ -23,7 +23,7 @@ func TestStyle(t *testing.T) {
|
|||||||
defer s.Fini()
|
defer s.Fini()
|
||||||
|
|
||||||
style := StyleDefault
|
style := StyleDefault
|
||||||
fg, bg, attr := style.Decompose()
|
fg, bg, attr := style.fg, style.bg, style.attrs
|
||||||
|
|
||||||
if fg != ColorDefault || bg != ColorDefault || attr != AttrNone {
|
if fg != ColorDefault || bg != ColorDefault || attr != AttrNone {
|
||||||
t.Errorf("Bad default style (%v, %v, %v)", fg, bg, attr)
|
t.Errorf("Bad default style (%v, %v, %v)", fg, bg, attr)
|
||||||
@ -34,7 +34,7 @@ func TestStyle(t *testing.T) {
|
|||||||
Foreground(ColorBlue).
|
Foreground(ColorBlue).
|
||||||
Blink(true)
|
Blink(true)
|
||||||
|
|
||||||
fg, bg, attr = s2.Decompose()
|
fg, bg, attr = s2.fg, s2.bg, s2.attrs
|
||||||
if fg != ColorBlue || bg != ColorRed || attr != AttrBlink {
|
if fg != ColorBlue || bg != ColorRed || attr != AttrBlink {
|
||||||
t.Errorf("Bad custom style (%v, %v, %v)", fg, bg, attr)
|
t.Errorf("Bad custom style (%v, %v, %v)", fg, bg, attr)
|
||||||
}
|
}
|
||||||
|
@ -238,6 +238,9 @@ type Terminfo struct {
|
|||||||
CurlyUnderline string // Smulx with param 3
|
CurlyUnderline string // Smulx with param 3
|
||||||
DottedUnderline string // Smulx with param 4
|
DottedUnderline string // Smulx with param 4
|
||||||
DashedUnderline string // Smulx with param 5
|
DashedUnderline string // Smulx with param 5
|
||||||
|
UnderlineColor string // Setuc1
|
||||||
|
UnderlineColorRGB string // Setulc
|
||||||
|
UnderlineColorReset string // ol
|
||||||
XTermLike bool // (XT) has XTerm extensions
|
XTermLike bool // (XT) has XTerm extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
63
tscreen.go
63
tscreen.go
@ -32,7 +32,6 @@ import (
|
|||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2/terminfo"
|
"github.com/gdamore/tcell/v2/terminfo"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewTerminfoScreen returns a Screen that uses the stock TTY interface
|
// NewTerminfoScreen returns a Screen that uses the stock TTY interface
|
||||||
@ -156,6 +155,9 @@ type tScreen struct {
|
|||||||
curlyUnder string
|
curlyUnder string
|
||||||
dottedUnder string
|
dottedUnder string
|
||||||
dashedUnder string
|
dashedUnder string
|
||||||
|
underColor string
|
||||||
|
underRGB string
|
||||||
|
underFg string
|
||||||
cursorStyles map[CursorStyle]string
|
cursorStyles map[CursorStyle]string
|
||||||
cursorStyle CursorStyle
|
cursorStyle CursorStyle
|
||||||
saved *term.State
|
saved *term.State
|
||||||
@ -356,20 +358,44 @@ func (t *tScreen) prepareUnderlines() {
|
|||||||
}
|
}
|
||||||
if t.ti.CurlyUnderline != "" {
|
if t.ti.CurlyUnderline != "" {
|
||||||
t.curlyUnder = t.ti.CurlyUnderline
|
t.curlyUnder = t.ti.CurlyUnderline
|
||||||
} else {
|
} else if t.ti.XTermLike {
|
||||||
t.curlyUnder = "\x1b[4:3m"
|
t.curlyUnder = "\x1b[4:3m"
|
||||||
}
|
}
|
||||||
if t.ti.DottedUnderline != "" {
|
if t.ti.DottedUnderline != "" {
|
||||||
t.dottedUnder = t.ti.DottedUnderline
|
t.dottedUnder = t.ti.DottedUnderline
|
||||||
} else {
|
} else if t.ti.XTermLike {
|
||||||
t.dottedUnder = "\x1b[4:4m"
|
t.dottedUnder = "\x1b[4:4m"
|
||||||
}
|
}
|
||||||
if t.ti.DashedUnderline != "" {
|
if t.ti.DashedUnderline != "" {
|
||||||
t.dashedUnder = t.ti.DashedUnderline
|
t.dashedUnder = t.ti.DashedUnderline
|
||||||
} else {
|
} else if t.ti.XTermLike {
|
||||||
t.dashedUnder = "\x1b[4:5m"
|
t.dashedUnder = "\x1b[4:5m"
|
||||||
}
|
}
|
||||||
// Still TODO: Underline Color
|
|
||||||
|
// Underline colors. We're not going to rely upon terminfo for this
|
||||||
|
// Essentially all terminals that support the curly underlines are
|
||||||
|
// expected to also support coloring them too - which reflects actual
|
||||||
|
// practice since these were introduced at about the same time.
|
||||||
|
if t.ti.UnderlineColor != "" {
|
||||||
|
t.underColor = t.ti.UnderlineColor
|
||||||
|
} else if t.ti.CurlyUnderline != "" {
|
||||||
|
t.underColor = "\x1b[58:5:%p1%dm"
|
||||||
|
}
|
||||||
|
if t.ti.UnderlineColorRGB != "" {
|
||||||
|
// An interesting wart here is that in order to facilitate
|
||||||
|
// using just a single parameter, the Setulc parameter takes
|
||||||
|
// the 24-bit color as an integer rather than separate bytes.
|
||||||
|
// This matches the "new" style direct color approach that
|
||||||
|
// ncurses took, even though everyone else when another way.
|
||||||
|
t.underRGB = t.ti.UnderlineColorRGB
|
||||||
|
} else if t.ti.CurlyUnderline != "" {
|
||||||
|
t.underRGB = "\x1b[58:2::%p1%d:%p2%d:%p3%dm"
|
||||||
|
}
|
||||||
|
if t.ti.UnderlineColorReset != "" {
|
||||||
|
t.underFg = t.ti.UnderlineColorReset
|
||||||
|
} else if t.ti.CurlyUnderline != "" {
|
||||||
|
t.underFg = "\x1b[59m"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tScreen) prepareExtendedOSC() {
|
func (t *tScreen) prepareExtendedOSC() {
|
||||||
@ -435,7 +461,6 @@ func (t *tScreen) prepareCursorStyles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Still TODO: Cursor Color
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tScreen) prepareKey(key Key, val string) {
|
func (t *tScreen) prepareKey(key Key, val string) {
|
||||||
@ -771,7 +796,7 @@ func (t *tScreen) drawCell(x, y int) int {
|
|||||||
style = t.style
|
style = t.style
|
||||||
}
|
}
|
||||||
if style != t.curstyle {
|
if style != t.curstyle {
|
||||||
fg, bg, attrs := style.Decompose()
|
fg, bg, attrs, uc := style.fg, style.bg, style.attrs, style.under
|
||||||
|
|
||||||
t.TPuts(ti.AttrOff)
|
t.TPuts(ti.AttrOff)
|
||||||
|
|
||||||
@ -780,6 +805,27 @@ func (t *tScreen) drawCell(x, y int) int {
|
|||||||
t.TPuts(ti.Bold)
|
t.TPuts(ti.Bold)
|
||||||
}
|
}
|
||||||
if attrs&(AttrUnderline|AttrDoubleUnderline|AttrCurlyUnderline|AttrDottedUnderline|AttrDashedUnderline) != 0 {
|
if attrs&(AttrUnderline|AttrDoubleUnderline|AttrCurlyUnderline|AttrDottedUnderline|AttrDashedUnderline) != 0 {
|
||||||
|
if uc.Valid() && (t.underColor != "" || t.underRGB != "") {
|
||||||
|
if uc == ColorReset {
|
||||||
|
t.TPuts(t.underFg)
|
||||||
|
} else if uc.IsRGB() {
|
||||||
|
if t.underRGB != "" {
|
||||||
|
r, g, b := uc.RGB()
|
||||||
|
t.TPuts(ti.TParm(t.underRGB, int(r), int(g), int(b)))
|
||||||
|
} else {
|
||||||
|
if v, ok := t.colors[uc]; ok {
|
||||||
|
uc = v
|
||||||
|
} else {
|
||||||
|
v = FindColor(uc, t.palette)
|
||||||
|
t.colors[uc] = v
|
||||||
|
uc = v
|
||||||
|
}
|
||||||
|
t.TPuts(ti.TParm(t.underColor, int(uc&0xff)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.TPuts(ti.TParm(t.underColor, int(uc&0xff)))
|
||||||
|
}
|
||||||
|
}
|
||||||
t.TPuts(ti.Underline) // to ensure everyone gets at least a basic underline
|
t.TPuts(ti.Underline) // to ensure everyone gets at least a basic underline
|
||||||
if (attrs & AttrDoubleUnderline) != 0 {
|
if (attrs & AttrDoubleUnderline) != 0 {
|
||||||
t.TPuts(t.doubleUnder)
|
t.TPuts(t.doubleUnder)
|
||||||
@ -928,8 +974,7 @@ func (t *tScreen) Show() {
|
|||||||
func (t *tScreen) clearScreen() {
|
func (t *tScreen) clearScreen() {
|
||||||
t.TPuts(t.ti.AttrOff)
|
t.TPuts(t.ti.AttrOff)
|
||||||
t.TPuts(t.exitUrl)
|
t.TPuts(t.exitUrl)
|
||||||
fg, bg, _ := t.style.Decompose()
|
_ = t.sendFgBg(t.style.fg, t.style.bg, AttrNone)
|
||||||
_ = t.sendFgBg(fg, bg, AttrNone)
|
|
||||||
t.TPuts(t.ti.Clear)
|
t.TPuts(t.ti.Clear)
|
||||||
t.clear = false
|
t.clear = false
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user