Added visible cursor support.

This commit is contained in:
Daniel Schmidt 2023-05-18 18:23:38 +02:00
parent c347038c5d
commit 19757d9b62
9 changed files with 60 additions and 17 deletions

View File

@ -36,7 +36,7 @@ func main() {
}
// Just pass your tea.Model to the bubbleadapter, and it will render it to the terminal.
win, err := bubbleadapter.Window(1000, 600, fonts, someModel{}, color.Black, tea.WithAltScreen())
win, _, err := bubbleadapter.Window(1000, 600, fonts, someModel{}, color.Black, tea.WithAltScreen())
if err != nil {
panic(err)
}

View File

@ -31,7 +31,7 @@ func (f fakeEnviron) Getenv(s string) string {
// Window creates a new crt based bubbletea window with the given width, height, fonts, model and default background color.
// Additional options can be passed to the bubbletea program.
func Window(width int, height int, fonts crt.Fonts, model tea.Model, defaultBg color.Color, options ...tea.ProgramOption) (*crt.Window, error) {
func Window(width int, height int, fonts crt.Fonts, model tea.Model, defaultBg color.Color, options ...tea.ProgramOption) (*crt.Window, *tea.Program, error) {
gameInput := crt.NewConcurrentRW()
gameOutput := crt.NewConcurrentRW()
@ -56,5 +56,6 @@ func Window(width int, height int, fonts crt.Fonts, model tea.Model, defaultBg c
crt.SysKill()
}()
return crt.NewGame(width, height, fonts, gameOutput, NewAdapter(prog), defaultBg)
win, err := crt.NewGame(width, height, fonts, gameOutput, NewAdapter(prog), defaultBg)
return win, prog, err
}

46
crt.go
View File

@ -30,14 +30,17 @@ type Window struct {
tty io.Reader
// Terminal cursor and color states.
cursorX int
cursorY int
mouseCellX int
mouseCellY int
defaultBg color.Color
curFg color.Color
curBg color.Color
curWeight FontWeight
cursorChar string
cursorColor color.Color
showCursor bool
cursorX int
cursorY int
mouseCellX int
mouseCellY int
defaultBg color.Color
curFg color.Color
curBg color.Color
curWeight FontWeight
// Other
showTps bool
@ -45,7 +48,6 @@ type Window struct {
bgColors *image.RGBA
shader []shader.Shader
routine sync.Once
tick float64
}
// NewGame creates a new terminal game with the given dimensions and font faces.
@ -89,6 +91,8 @@ func NewGame(width int, height int, fonts Fonts, tty io.Reader, adapter InputAda
grid: grid,
tty: tty,
bgColors: image.NewRGBA(image.Rect(0, 0, cellsWidth*cellWidth, cellsHeight*cellHeight)),
cursorChar: "█",
cursorColor: color.RGBA{R: 255, G: 255, B: 255, A: 100},
}
game.inputAdapter.HandleWindowSize(WindowSize{
@ -102,6 +106,21 @@ func NewGame(width int, height int, fonts Fonts, tty io.Reader, adapter InputAda
return game, nil
}
// SetShowCursor enables or disables the cursor.
func (g *Window) SetShowCursor(val bool) {
g.showCursor = val
}
// SetCursorChar sets the character that is used for the cursor.
func (g *Window) SetCursorChar(char string) {
g.cursorChar = char
}
// SetCursorColor sets the color of the cursor.
func (g *Window) SetCursorColor(color color.Color) {
g.cursorColor = color
}
// SetShader sets a shader that is applied to the whole screen.
func (g *Window) SetShader(shader ...shader.Shader) {
g.shader = shader
@ -215,6 +234,10 @@ func (g *Window) handleCSI(csi any) {
g.SetBgPixels(i, g.cursorY, g.defaultBg)
}
}
case CursorShowSeq:
g.SetShowCursor(true)
case CursorHideSeq:
g.SetShowCursor(false)
case ScrollUpSeq:
fmt.Println("UNSUPPORTED: ScrollUpSeq", seq.Count)
case ScrollDownSeq:
@ -447,7 +470,10 @@ func (g *Window) Draw(screen *ebiten.Image) {
}
}
g.tick += 1 / 60.0
if g.showCursor {
text.Draw(bufferImage, g.cursorChar, g.fonts.Normal, g.cursorX*g.cellWidth, g.cursorY*g.cellHeight+g.cellOffsetY, g.cursorColor)
}
if g.shader != nil {
for i := range g.shader {
_ = g.shader[i].Apply(screen, bufferImage)

11
csi.go
View File

@ -72,6 +72,10 @@ type DeleteLineSeq struct {
Count int
}
type CursorShowSeq struct{}
type CursorHideSeq struct{}
// extractCSI extracts a CSI sequence from the beginning of a string.
// It returns the sequence without any suffix, and a boolean indicating
// whether a sequence was found.
@ -105,6 +109,13 @@ func parseCSI(s string) (any, bool) {
return nil, false
}
switch s {
case termenv.ShowCursorSeq:
return CursorShowSeq{}, true
case termenv.HideCursorSeq:
return CursorHideSeq{}, true
}
switch s[len(s)-1] {
case 'A':
if count, err := strconv.Atoi(s[:len(s)-1]); err == nil {

View File

@ -15,6 +15,7 @@ func TestCSI(t *testing.T) {
testString += fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, 1, 2)
testString += fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, 1, 2)
testString += "HELLO WORLD"
testString += termenv.CSI + termenv.ShowCursorSeq
testString += fmt.Sprintf(termenv.CSI+termenv.CursorPositionSeq, 1, 2)
testString += fmt.Sprintf(termenv.CSI+termenv.CursorBackSeq, 5)
@ -34,6 +35,7 @@ func TestCSI(t *testing.T) {
EraseDisplaySeq{Type: 20},
CursorPositionSeq{Row: 1, Col: 2},
CursorPositionSeq{Row: 1, Col: 2},
CursorShowSeq{},
CursorPositionSeq{Row: 1, Col: 2},
CursorBackSeq{Count: 5},
}, sequences)

View File

@ -43,7 +43,7 @@ func main() {
panic(err)
}
win, err := bubbleadapter.Window(Width, Height, fonts, model{}, color.Black)
win, _, err := bubbleadapter.Window(Width, Height, fonts, model{}, color.Black)
if err != nil {
panic(err)
}

View File

@ -192,7 +192,7 @@ func main() {
panic(err)
}
win, err := bubbleadapter.Window(Width, Height, fonts, newModel(), color.Black)
win, _, err := bubbleadapter.Window(Width, Height, fonts, newModel(), color.Black)
if err != nil {
panic(err)
}

View File

@ -193,7 +193,7 @@ func main() {
panic(err)
}
win, err := bubbleadapter.Window(Width, Height, fonts, newModel(), color.Black)
win, _, err := bubbleadapter.Window(Width, Height, fonts, newModel(), color.Black)
if err != nil {
panic(err)
}

View File

@ -34,11 +34,14 @@ func main() {
panic(err)
}
win, err := bubbleadapter.Window(Width, Height, fonts, model{}, color.Black, tea.WithAltScreen())
win, prog, err := bubbleadapter.Window(Width, Height, fonts, model{}, color.Black, tea.WithAltScreen())
if err != nil {
panic(err)
}
prog.Send(tea.ShowCursor())
win.SetCursorChar("_")
if err := win.Run("Simple"); err != nil {
panic(err)
}