1
0
mirror of https://github.com/gdamore/tcell.git synced 2025-04-26 13:48:53 +08:00

fixes #666 cursor color

This adds a new optional parameter to screen.SetCursorStyle,
which is a color.  The cursors demo is enhanced to show this.

This ability is supported on screen types, provided the underlying
terminal supports the capability.
This commit is contained in:
Garrett D'Amore 2024-03-07 07:28:12 -08:00
parent 652ba11803
commit 887cf2766e
9 changed files with 92 additions and 29 deletions

View File

@ -15,7 +15,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// beep makes a beep every second until you press ESC
package main package main
import ( import (
@ -52,6 +51,7 @@ func main() {
style := tcell.StyleDefault style := tcell.StyleDefault
go func() { go func() {
for { for {
s.Show()
ev := s.PollEvent() ev := s.PollEvent()
switch ev := ev.(type) { switch ev := ev.(type) {
case *tcell.EventKey: case *tcell.EventKey:
@ -60,27 +60,26 @@ func main() {
switch ev.Rune() { switch ev.Rune() {
case '0': case '0':
s.SetContent(2, 2, '0', nil, style) s.SetContent(2, 2, '0', nil, style)
s.SetCursorStyle(tcell.CursorStyleDefault) s.SetCursorStyle(tcell.CursorStyleDefault, tcell.ColorReset)
case '1': case '1':
s.SetContent(2, 2, '1', nil, style) s.SetContent(2, 2, '1', nil, style)
s.SetCursorStyle(tcell.CursorStyleBlinkingBlock) s.SetCursorStyle(tcell.CursorStyleBlinkingBlock, tcell.ColorGreen)
case '2': case '2':
s.SetCell(2, 2, tcell.StyleDefault, '2') s.SetCell(2, 2, tcell.StyleDefault, '2')
s.SetCursorStyle(tcell.CursorStyleSteadyBlock) s.SetCursorStyle(tcell.CursorStyleSteadyBlock, tcell.ColorBlue)
case '3': case '3':
s.SetCell(2, 2, tcell.StyleDefault, '3') s.SetCell(2, 2, tcell.StyleDefault, '3')
s.SetCursorStyle(tcell.CursorStyleBlinkingUnderline) s.SetCursorStyle(tcell.CursorStyleBlinkingUnderline, tcell.ColorRed)
case '4': case '4':
s.SetCell(2, 2, tcell.StyleDefault, '4') s.SetCell(2, 2, tcell.StyleDefault, '4')
s.SetCursorStyle(tcell.CursorStyleSteadyUnderline) s.SetCursorStyle(tcell.CursorStyleSteadyUnderline, tcell.ColorOrange)
case '5': case '5':
s.SetCell(2, 2, tcell.StyleDefault, '5') s.SetCell(2, 2, tcell.StyleDefault, '5')
s.SetCursorStyle(tcell.CursorStyleBlinkingBar) s.SetCursorStyle(tcell.CursorStyleBlinkingBar, tcell.ColorYellow)
case '6': case '6':
s.SetCell(2, 2, tcell.StyleDefault, '6') s.SetCell(2, 2, tcell.StyleDefault, '6')
s.SetCursorStyle(tcell.CursorStyleSteadyBar) s.SetCursorStyle(tcell.CursorStyleSteadyBar, tcell.ColorPink)
} }
s.Show()
case tcell.KeyEscape, tcell.KeyEnter, tcell.KeyCtrlC: case tcell.KeyEscape, tcell.KeyEnter, tcell.KeyCtrlC:
close(quit) close(quit)

View File

@ -49,6 +49,7 @@ type cScreen struct {
oscreen consoleInfo oscreen consoleInfo
ocursor cursorInfo ocursor cursorInfo
cursorStyle CursorStyle cursorStyle CursorStyle
cursorColor Color
oimode uint32 oimode uint32
oomode uint32 oomode uint32
cells CellBuffer cells CellBuffer
@ -173,6 +174,8 @@ const (
vtUnderColorReset = "\x1b[59m" vtUnderColorReset = "\x1b[59m"
vtEnterUrl = "\x1b]8;%s;%s\x1b\\" // NB arg 1 is id, arg 2 is url vtEnterUrl = "\x1b]8;%s;%s\x1b\\" // NB arg 1 is id, arg 2 is url
vtExitUrl = "\x1b]8;;\x1b\\" vtExitUrl = "\x1b]8;;\x1b\\"
vtCursorColorRGB = "\x1b]12;#%02x%02x%02x\007"
vtCursorColorReset = "\x1b]112\007"
) )
var vtCursorStyles = map[CursorStyle]string{ var vtCursorStyles = map[CursorStyle]string{
@ -344,6 +347,7 @@ func (s *cScreen) disengage() {
if s.vten { if s.vten {
s.emitVtString(vtCursorStyles[CursorStyleDefault]) s.emitVtString(vtCursorStyles[CursorStyleDefault])
s.emitVtString(vtCursorColorReset)
s.emitVtString(vtEnableAm) s.emitVtString(vtEnableAm)
if !s.disableAlt { if !s.disableAlt {
s.emitVtString(vtExitCA) s.emitVtString(vtExitCA)
@ -435,6 +439,12 @@ func (s *cScreen) showCursor() {
if s.vten { if s.vten {
s.emitVtString(vtShowCursor) s.emitVtString(vtShowCursor)
s.emitVtString(vtCursorStyles[s.cursorStyle]) s.emitVtString(vtCursorStyles[s.cursorStyle])
if s.cursorColor == ColorReset {
s.emitVtString(vtCursorColorReset)
} else if s.cursorColor.Valid() {
r, g, b := s.cursorColor.RGB()
s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b))
}
} else { } else {
s.setCursorInfo(&cursorInfo{size: 100, visible: 1}) s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
} }
@ -458,11 +468,12 @@ func (s *cScreen) ShowCursor(x, y int) {
s.Unlock() s.Unlock()
} }
func (s *cScreen) SetCursorStyle(cs CursorStyle) { func (s *cScreen) SetCursor(cs CursorStyle, cc Color) {
s.Lock() s.Lock()
if !s.fini { if !s.fini {
if _, ok := vtCursorStyles[cs]; ok { if _, ok := vtCursorStyles[cs]; ok {
s.cursorStyle = cs s.cursorStyle = cs
s.cursorColor = cc
s.doCursor() s.doCursor()
} }
} }
@ -1100,7 +1111,6 @@ func (s *cScreen) setCursorInfo(info *cursorInfo) {
_, _, _ = procSetConsoleCursorInfo.Call( _, _, _ = procSetConsoleCursorInfo.Call(
uintptr(s.out), uintptr(s.out),
uintptr(unsafe.Pointer(info))) uintptr(unsafe.Pointer(info)))
} }
func (s *cScreen) setCursorPos(x, y int, vtEnable bool) { func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {

View File

@ -1,4 +1,4 @@
// Copyright 2023 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.
@ -79,8 +79,9 @@ type Screen interface {
// SetCursorStyle is used to set the cursor style. If the style // SetCursorStyle is used to set the cursor style. If the style
// is not supported (or cursor styles are not supported at all), // is not supported (or cursor styles are not supported at all),
// then this will have no effect. // then this will have no effect. Color will be changed if supplied,
SetCursorStyle(CursorStyle) // and the terminal supports doing so.
SetCursorStyle(CursorStyle, ...Color)
// Size returns the screen size as width, height. This changes in // Size returns the screen size as width, height. This changes in
// response to a call to Clear or Flush. // response to a call to Clear or Flush.
@ -312,7 +313,7 @@ type screenImpl interface {
SetStyle(style Style) SetStyle(style Style)
ShowCursor(x int, y int) ShowCursor(x int, y int)
HideCursor() HideCursor()
SetCursorStyle(CursorStyle) SetCursor(CursorStyle, Color)
Size() (width, height int) Size() (width, height int)
EnableMouse(...MouseFlags) EnableMouse(...MouseFlags)
DisableMouse() DisableMouse()
@ -464,3 +465,11 @@ func (b *baseScreen) PostEvent(ev Event) error {
return ErrEventQFull return ErrEventQFull
} }
} }
func (b *baseScreen) SetCursorStyle(cs CursorStyle, ccs ...Color) {
if len(ccs) > 0 {
b.SetCursor(cs, ccs[0])
} else {
b.SetCursor(cs, ColorNone)
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2023 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.
@ -239,7 +239,7 @@ func (s *simscreen) hideCursor() {
s.cursorvis = false s.cursorvis = false
} }
func (s *simscreen) SetCursorStyle(CursorStyle) {} func (s *simscreen) SetCursor(CursorStyle, Color) {}
func (s *simscreen) Show() { func (s *simscreen) Show() {
s.Lock() s.Lock()

View File

@ -227,6 +227,9 @@ type Terminfo struct {
CursorSteadyUnderline string CursorSteadyUnderline string
CursorBlinkingBar string CursorBlinkingBar string
CursorSteadyBar string CursorSteadyBar string
CursorColor string // nothing uses it yet
CursorColorRGB string // Cs (but not really because Cs uses X11 color string)
CursorColorReset string // Cr
EnterUrl string EnterUrl string
ExitUrl string ExitUrl string
SetWindowSize string SetWindowSize string

View File

@ -160,6 +160,9 @@ type tScreen struct {
underFg string underFg string
cursorStyles map[CursorStyle]string cursorStyles map[CursorStyle]string
cursorStyle CursorStyle cursorStyle CursorStyle
cursorColor Color
cursorRGB string
cursorFg string
saved *term.State saved *term.State
stopQ chan struct{} stopQ chan struct{}
eventQ chan Event eventQ chan Event
@ -460,7 +463,20 @@ func (t *tScreen) prepareCursorStyles() {
CursorStyleSteadyBar: "\x1b[6 q", CursorStyleSteadyBar: "\x1b[6 q",
} }
} }
if t.ti.CursorColorRGB != "" {
// if it was X11 style with just a single %p1%s, then convert
t.cursorRGB = t.ti.CursorColorRGB
}
if t.ti.CursorColorReset != "" {
t.cursorFg = t.ti.CursorColorReset
}
if t.cursorRGB == "" {
t.cursorRGB = "\x1b]12;%p1%s\007"
t.cursorFg = "\x1b]112\007"
}
// convert XTERM style color names to RGB color code. We have no way to do palette colors
t.cursorRGB = strings.Replace(t.cursorRGB, "%p1%s", "#%p1%02x%p2%02x%p3%02x", 1)
} }
func (t *tScreen) prepareKey(key Key, val string) { func (t *tScreen) prepareKey(key Key, val string) {
@ -912,9 +928,10 @@ func (t *tScreen) ShowCursor(x, y int) {
t.Unlock() t.Unlock()
} }
func (t *tScreen) SetCursorStyle(cs CursorStyle) { func (t *tScreen) SetCursor(cs CursorStyle, cc Color) {
t.Lock() t.Lock()
t.cursorStyle = cs t.cursorStyle = cs
t.cursorColor = cc
t.Unlock() t.Unlock()
} }
@ -937,6 +954,14 @@ func (t *tScreen) showCursor() {
t.TPuts(esc) t.TPuts(esc)
} }
} }
if t.cursorRGB != "" {
if t.cursorColor == ColorReset {
t.TPuts(t.cursorFg)
} else if t.cursorColor.Valid() {
r, g, b := t.cursorColor.RGB()
t.TPuts(t.ti.TParm(t.cursorRGB, int(r), int(g), int(b)))
}
}
t.cx = x t.cx = x
t.cy = y t.cy = y
} }
@ -1954,6 +1979,9 @@ func (t *tScreen) disengage() {
if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault { if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault {
t.TPuts(t.cursorStyles[CursorStyleDefault]) t.TPuts(t.cursorStyles[CursorStyleDefault])
} }
if t.cursorFg != "" && t.cursorColor.Valid() {
t.TPuts(t.cursorFg)
}
t.TPuts(ti.ResetFgBg) t.TPuts(ti.ResetFgBg)
t.TPuts(ti.AttrOff) t.TPuts(ti.AttrOff)
t.TPuts(ti.ExitKeypad) t.TPuts(ti.ExitKeypad)

View File

@ -21,6 +21,7 @@ const beepAudio = new Audio("beep.wav");
var cx = -1; var cx = -1;
var cy = -1; var cy = -1;
var cursorClass = "cursor-blinking-block"; var cursorClass = "cursor-blinking-block";
var cursorColor = "";
var content; // {data: row[height], dirty: bool} var content; // {data: row[height], dirty: bool}
// row = {data: element[width], previous: span} // row = {data: element[width], previous: span}
@ -185,12 +186,18 @@ function displayCursor() {
content.data[cy].data[cx] = span; content.data[cy].data[cx] = span;
} }
if (cursorColor != "") {
term.style.setProperty("--cursor-color", cursorColor);
} else {
term.style.setProperty("--cursor-color", "lightgrey");
}
content.data[cy].data[cx].classList.add(cursorClass); content.data[cy].data[cx].classList.add(cursorClass);
} }
} }
function setCursorStyle(newClass) { function setCursorStyle(newClass, newColor) {
if (newClass == cursorClass) { if (newClass == cursorClass && newColor == cursorColor) {
return; return;
} }
@ -207,6 +214,7 @@ function setCursorStyle(newClass) {
} }
cursorClass = newClass; cursorClass = newClass;
cursorColor = newColor;
} }
function beep() { function beep() {

View File

@ -17,6 +17,7 @@
-khtml-user-select: none; -khtml-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
--cursor-color: lightgrey;
} }
/* Style attributes */ /* Style attributes */
@ -64,26 +65,26 @@
/* Cursor styles */ /* Cursor styles */
.cursor-steady-block { .cursor-steady-block {
background-color: lightgrey !important; background-color: var(--cursor-color) !important;
} }
.cursor-blinking-block { .cursor-blinking-block {
animation: blinking-block 1s step-start infinite !important; animation: blinking-block 1s step-start infinite !important;
} }
@keyframes blinking-block { @keyframes blinking-block {
50% { 50% {
background-color: lightgrey; background-color: var(--cursor-color);
} }
} }
.cursor-steady-underline { .cursor-steady-underline {
text-decoration: underline lightgrey !important; text-decoration: underline var(--cursor-color) !important;
} }
.cursor-blinking-underline { .cursor-blinking-underline {
animation: blinking-underline 1s step-start infinite !important; animation: blinking-underline 1s step-start infinite !important;
} }
@keyframes blinking-underline { @keyframes blinking-underline {
50% { 50% {
text-decoration: underline lightgrey; text-decoration: underline var(--cursor-color);
} }
} }
@ -93,7 +94,7 @@
.cursor-steady-bar:before { .cursor-steady-bar:before {
content: " "; content: " ";
width: 2px; width: 2px;
background-color: lightgrey !important; background-color: var(--cursor-color) !important;
display: inline-block; display: inline-block;
} }
.cursor-blinking-bar { .cursor-blinking-bar {
@ -102,7 +103,7 @@
.cursor-blinking-bar:before { .cursor-blinking-bar:before {
content: " "; content: " ";
width: 2px; width: 2px;
background-color: lightgrey !important; background-color: var(--cursor-color) !important;
display: inline-block; display: inline-block;
animation: blinker 1s step-start infinite; animation: blinker 1s step-start infinite;
} }

View File

@ -19,11 +19,13 @@ package tcell
import ( import (
"errors" "errors"
"github.com/gdamore/tcell/v2/terminfo" "fmt"
"strings" "strings"
"sync" "sync"
"syscall/js" "syscall/js"
"unicode/utf8" "unicode/utf8"
"github.com/gdamore/tcell/v2/terminfo"
) )
func NewTerminfoScreen() (Screen, error) { func NewTerminfoScreen() (Screen, error) {
@ -158,9 +160,12 @@ func (t *wScreen) ShowCursor(x, y int) {
t.Unlock() t.Unlock()
} }
func (t *wScreen) SetCursorStyle(cs CursorStyle) { func (t *wScreen) SetCursor(cs CursorStyle, cc Color) {
if !cc.Valid() {
cc = ColorLightGray
}
t.Lock() t.Lock()
js.Global().Call("setCursorStyle", curStyleClasses[cs]) js.Global().Call("setCursorStyle", curStyleClasses[cs], fmt.Sprintf("#%06x", cc.Hex()))
t.Unlock() t.Unlock()
} }