232 lines
4.6 KiB
Go
232 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gdamore/tcell"
|
|
)
|
|
|
|
type typer struct {
|
|
Scr tcell.Screen
|
|
OnStart func()
|
|
|
|
currentWordStyle tcell.Style
|
|
nextWordStyle tcell.Style
|
|
incorrectSpaceStyle tcell.Style
|
|
incorrectStyle tcell.Style
|
|
correctStyle tcell.Style
|
|
backgroundStyle tcell.Style
|
|
}
|
|
|
|
func NewTyper(scr tcell.Screen, fgcol, bgcol, hicol, hicol2, hicol3, errcol tcell.Color) *typer {
|
|
def := tcell.StyleDefault.
|
|
Foreground(fgcol).
|
|
Background(bgcol)
|
|
|
|
return &typer{
|
|
Scr: scr,
|
|
backgroundStyle: def,
|
|
correctStyle: def.Foreground(hicol),
|
|
currentWordStyle: def.Foreground(hicol2),
|
|
nextWordStyle: def.Foreground(hicol3),
|
|
incorrectStyle: def.Foreground(errcol),
|
|
incorrectSpaceStyle: def.Background(errcol),
|
|
}
|
|
}
|
|
|
|
func (t *typer) highlight(text []cell, idx int, currentWordStyle, nextWorkStyle tcell.Style) {
|
|
for ; idx < len(text) && text[idx].c != ' '; idx++ {
|
|
text[idx].style = currentWordStyle
|
|
}
|
|
|
|
for ; idx < len(text) && text[idx].c == ' '; idx++ {
|
|
}
|
|
|
|
for ; idx < len(text) && text[idx].c != ' '; idx++ {
|
|
text[idx].style = nextWorkStyle
|
|
}
|
|
}
|
|
|
|
func (t *typer) Start(text []string) (nerrs, ncorrect int, tim time.Duration, complete bool) {
|
|
var startTime time.Time
|
|
|
|
for i, p := range text {
|
|
var fn func() = nil
|
|
if i == 0 {
|
|
fn = func() {
|
|
startTime = time.Now()
|
|
}
|
|
}
|
|
|
|
e, c, completed := t.start(p, fn)
|
|
|
|
nerrs += e
|
|
ncorrect += c
|
|
|
|
if !completed {
|
|
tim = time.Now().Sub(startTime)
|
|
complete = false
|
|
return
|
|
}
|
|
}
|
|
|
|
tim = time.Now().Sub(startTime)
|
|
complete = true
|
|
|
|
return
|
|
}
|
|
|
|
func (t *typer) start(s string, onStart func()) (int, int, bool) {
|
|
started := false
|
|
text := stringToCells(s)
|
|
for i, _ := range text {
|
|
text[i].style = t.backgroundStyle
|
|
}
|
|
|
|
fmt.Printf("\033[5 q")
|
|
|
|
//Assumes original cursor shape was a block (the one true cursor shape), there doesn't appear to be a
|
|
//good way to save/restore the shape if the user has changed it from the otcs.
|
|
defer fmt.Printf("\033[2 q")
|
|
|
|
t.Scr.SetStyle(t.backgroundStyle)
|
|
idx := 0
|
|
|
|
redraw := func() {
|
|
//Potentially inefficient, but seems to be good enough
|
|
drawCellsAtCenter(t.Scr, text, idx)
|
|
t.Scr.Show()
|
|
}
|
|
|
|
deleteWord := func() {
|
|
t.highlight(text, idx, t.backgroundStyle, t.backgroundStyle)
|
|
|
|
if idx == 0 {
|
|
return
|
|
}
|
|
|
|
idx--
|
|
|
|
for idx > 0 && (text[idx].c == ' ' || text[idx].c == '\n') {
|
|
text[idx].style = t.backgroundStyle
|
|
idx--
|
|
}
|
|
|
|
for idx > 0 && text[idx].c != ' ' && text[idx].c != '\n' {
|
|
text[idx].style = t.backgroundStyle
|
|
idx--
|
|
}
|
|
|
|
if text[idx].c == ' ' || text[idx].c == '\n' {
|
|
idx++
|
|
}
|
|
|
|
t.highlight(text, idx, t.currentWordStyle, t.nextWordStyle)
|
|
}
|
|
|
|
for {
|
|
t.highlight(text, idx, t.currentWordStyle, t.nextWordStyle)
|
|
redraw()
|
|
|
|
ev := t.Scr.PollEvent()
|
|
|
|
switch ev := ev.(type) {
|
|
case *tcell.EventResize:
|
|
t.Scr.Sync()
|
|
t.Scr.Clear()
|
|
case *tcell.EventKey:
|
|
if !started {
|
|
if onStart != nil {
|
|
onStart()
|
|
}
|
|
|
|
started = true
|
|
}
|
|
|
|
switch ev.Key() {
|
|
case tcell.KeyCtrlC:
|
|
fmt.Printf("\033[2 q")
|
|
t.Scr.Fini()
|
|
os.Exit(1)
|
|
case tcell.KeyEscape:
|
|
return -1, -1, false
|
|
case tcell.KeyCtrlL:
|
|
t.Scr.Sync()
|
|
case tcell.KeyCtrlH:
|
|
deleteWord()
|
|
|
|
case tcell.KeyBackspace2:
|
|
if ev.Modifiers() == tcell.ModAlt {
|
|
deleteWord()
|
|
} else {
|
|
t.highlight(text, idx, t.backgroundStyle, t.backgroundStyle)
|
|
|
|
if idx == 0 {
|
|
break
|
|
}
|
|
|
|
if idx < len(text) {
|
|
text[idx].style = t.backgroundStyle
|
|
}
|
|
|
|
idx--
|
|
|
|
for idx > 0 && text[idx].c == '\n' {
|
|
text[idx].style = t.backgroundStyle
|
|
idx--
|
|
}
|
|
|
|
t.highlight(text, idx, t.currentWordStyle, t.nextWordStyle)
|
|
}
|
|
case tcell.KeyRune:
|
|
if idx < len(text) {
|
|
switch {
|
|
case ev.Rune() == text[idx].c:
|
|
text[idx].style = t.correctStyle
|
|
idx++
|
|
case ev.Rune() == ' ':
|
|
for idx < len(text) && text[idx].c != ' ' && text[idx].c != '\n' {
|
|
text[idx].style = t.incorrectStyle
|
|
idx++
|
|
}
|
|
|
|
if idx < len(text) {
|
|
text[idx].style = t.incorrectSpaceStyle
|
|
idx++
|
|
}
|
|
case text[idx].c == ' ':
|
|
text[idx].style = t.incorrectSpaceStyle
|
|
idx++
|
|
default:
|
|
text[idx].style = t.incorrectStyle
|
|
idx++
|
|
}
|
|
|
|
for idx < len(text) && text[idx].c == '\n' {
|
|
idx++
|
|
}
|
|
|
|
t.highlight(text, idx, t.currentWordStyle, t.nextWordStyle)
|
|
}
|
|
|
|
if idx == len(text) {
|
|
nerrs := 0
|
|
|
|
for _, c := range text {
|
|
if c.style == t.incorrectStyle || c.style == t.incorrectSpaceStyle {
|
|
nerrs++
|
|
}
|
|
}
|
|
|
|
ncorrect := len(text) - nerrs
|
|
|
|
t.Scr.Clear()
|
|
return nerrs, ncorrect, true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|