summaryrefslogtreecommitdiff
path: root/ui/ui.go
diff options
context:
space:
mode:
authorHubert Hirtz <hubert.hirtz@laposte.net>2020-05-31 23:20:18 +0200
committerHubert Hirtz <hubert.hirtz@laposte.net>2020-06-03 15:41:51 +0200
commit881d63465cdb17357438763d4d2996d5f1d92fcd (patch)
tree36b1b7c852105d34ee2a8d71f6967b3e448ba3b7 /ui/ui.go
Initial commit
Diffstat (limited to 'ui/ui.go')
-rw-r--r--ui/ui.go494
1 files changed, 494 insertions, 0 deletions
diff --git a/ui/ui.go b/ui/ui.go
new file mode 100644
index 0000000..7bdf4a7
--- /dev/null
+++ b/ui/ui.go
@@ -0,0 +1,494 @@
+package ui
+
+import (
+ "github.com/gdamore/tcell"
+ "github.com/mattn/go-runewidth"
+ "sync/atomic"
+ "time"
+)
+
+type UI struct {
+ screen tcell.Screen
+ Events chan tcell.Event
+ exit atomic.Value // bool
+
+ bufferList BufferList
+ textInput []rune
+ textCursor int
+}
+
+func New() (ui *UI, err error) {
+ ui = &UI{}
+
+ ui.screen, err = tcell.NewScreen()
+ if err != nil {
+ return
+ }
+
+ err = ui.screen.Init()
+ if err != nil {
+ return
+ }
+
+ _, h := ui.screen.Size()
+ ui.screen.Clear()
+ ui.screen.ShowCursor(0, h-2)
+
+ ui.Events = make(chan tcell.Event)
+ go func() {
+ for !ui.ShouldExit() {
+ ui.Events <- ui.screen.PollEvent()
+ }
+ }()
+
+ ui.exit.Store(false)
+
+ ui.bufferList = BufferList{
+ List: []Buffer{
+ {
+ Title: "home",
+ Highlights: 0,
+ Content: []Line{{
+ Time: time.Now(),
+ Content: homeMessages[0],
+ }},
+ },
+ },
+ }
+
+ ui.textInput = []rune{}
+
+ ui.Draw()
+
+ return
+}
+
+func (ui *UI) ShouldExit() bool {
+ return ui.exit.Load().(bool)
+}
+
+func (ui *UI) Exit() {
+ ui.exit.Store(true)
+}
+
+func (ui *UI) Close() {
+ ui.screen.Fini()
+}
+
+func (ui *UI) CurrentBuffer() (title string) {
+ title = ui.bufferList.List[ui.bufferList.Current].Title
+ return
+}
+
+func (ui *UI) NextBuffer() {
+ ok := ui.bufferList.Next()
+ if ok {
+ ui.drawBuffer()
+ ui.drawStatus()
+ }
+}
+
+func (ui *UI) PreviousBuffer() {
+ ok := ui.bufferList.Previous()
+ if ok {
+ ui.drawBuffer()
+ ui.drawStatus()
+ }
+}
+
+func (ui *UI) AddBuffer(title string) {
+ _, ok := ui.bufferList.Add(title)
+ if ok {
+ ui.drawStatus()
+ ui.drawBuffer() // TODO only invalidate buffer list
+ }
+}
+
+func (ui *UI) RemoveBuffer(title string) {
+ ok := ui.bufferList.Remove(title)
+ if ok {
+ ui.drawStatus()
+ ui.drawBuffer()
+ }
+}
+
+func (ui *UI) AddLine(buffer string, line string, t time.Time) {
+ idx := ui.bufferList.Idx(buffer)
+ if idx < 0 {
+ return
+ }
+
+ ui.bufferList.AddLine(idx, line, t)
+
+ if idx == ui.bufferList.Current {
+ ui.drawBuffer()
+ }
+}
+
+func (ui *UI) InputRune(r rune) {
+ ui.textInput = append(ui.textInput, r)
+ ui.textCursor++
+ ui.drawEditor()
+}
+
+func (ui *UI) InputRight() {
+ if ui.textCursor < len(ui.textInput) {
+ ui.textCursor++
+ ui.drawEditor()
+ }
+}
+
+func (ui *UI) InputLeft() {
+ if 0 < ui.textCursor {
+ ui.textCursor--
+ ui.drawEditor()
+ }
+}
+
+func (ui *UI) InputBackspace() {
+ if 0 < len(ui.textInput) {
+ ui.textInput = ui.textInput[:len(ui.textInput)-1]
+ if len(ui.textInput) < ui.textCursor {
+ ui.textCursor = len(ui.textInput)
+ }
+ ui.drawEditor()
+ }
+}
+
+func (ui *UI) InputEnter() (content string) {
+ content = string(ui.textInput)
+
+ ui.textInput = []rune{}
+ ui.textCursor = 0
+ ui.drawEditor()
+
+ return
+}
+
+func (ui *UI) Draw() {
+ ui.drawStatus()
+ ui.drawEditor()
+ ui.drawBuffer()
+}
+
+func (ui *UI) drawEditor() {
+ st := tcell.StyleDefault
+ w, h := ui.screen.Size()
+ if w == 0 {
+ return
+ }
+
+ s := string(ui.textInput)
+ sw := runewidth.StringWidth(s)
+
+ x := 0
+ y := h - 2
+ i := 0
+
+ for ; w < sw+1 && i < len(ui.textInput); i++ {
+ r := ui.textInput[i]
+ rw := runewidth.RuneWidth(r)
+ sw -= rw
+ }
+
+ for ; i < len(ui.textInput); i++ {
+ r := ui.textInput[i]
+ ui.screen.SetContent(x, y, r, nil, st)
+ x += runewidth.RuneWidth(r)
+ }
+
+ for ; x < w; x++ {
+ ui.screen.SetContent(x, y, ' ', nil, st)
+ }
+
+ ui.screen.ShowCursor(ui.textCursor, y)
+ ui.screen.Show()
+}
+
+func (ui *UI) drawBuffer() {
+ st := tcell.StyleDefault
+ w, h := ui.screen.Size()
+ if h < 2 {
+ return
+ }
+
+ for x := 0; x < w; x++ {
+ for y := 0; y < h-2; y++ {
+ ui.screen.SetContent(x, y, ' ', nil, st)
+ }
+ }
+
+ b := ui.bufferList.List[ui.bufferList.Current]
+
+ if len(b.Content) == 0 {
+ return
+ }
+
+ y := h - 2
+ start := len(b.Content) - 1
+ for {
+ lw := runewidth.StringWidth(b.Content[start].Content)
+
+ if y-(lw/w+1) < 0 {
+ break
+ }
+
+ y -= lw/w + 1
+
+ if start <= 0 {
+ break
+ }
+ start--
+ }
+ if start > 0 {
+ start++
+ }
+
+ var bold, italic, underline bool
+ var colorState int
+ var fgColor, bgColor int
+
+ var lastHour, lastMinute int
+
+ for _, line := range b.Content[start:] {
+ rs := []rune(line.Content)
+ x := 0
+
+ hour := line.Time.Hour()
+ minute := line.Time.Minute()
+
+ if hour != lastHour || minute != lastMinute {
+ t := []rune{rune(hour/10) + '0', rune(hour%10) + '0', ':', rune(minute/10) + '0', rune(minute%10) + '0', ' '}
+ tst := tcell.StyleDefault.Bold(true)
+
+ for _, r := range t {
+ if w <= x {
+ y++
+ x = 0
+ }
+ if h-2 <= y {
+ break
+ }
+
+ ui.screen.SetContent(x, y, r, nil, tst)
+ x += runewidth.RuneWidth(r)
+ }
+
+ lastHour = hour
+ lastMinute = minute
+ }
+
+ for _, r := range rs {
+ if w <= x {
+ y++
+ x = 0
+ }
+ if h-2 <= y {
+ break
+ }
+
+ if colorState == 1 {
+ fgColor = 0
+ bgColor = 0
+ if '0' <= r && r <= '9' {
+ fgColor = fgColor*10 + int(r-'0')
+ colorState = 2
+ continue
+ }
+ st = st.Foreground(tcell.ColorDefault)
+ st = st.Background(tcell.ColorDefault)
+ colorState = 0
+ } else if colorState == 2 {
+ if '0' <= r && r <= '9' {
+ fgColor = fgColor*10 + int(r-'0')
+ colorState = 3
+ continue
+ }
+ if r == ',' {
+ colorState = 4
+ continue
+ }
+ c := colorFromCode(fgColor)
+ st = st.Foreground(c)
+ colorState = 0
+ } else if colorState == 3 {
+ if r == ',' {
+ colorState = 4
+ continue
+ }
+ c := colorFromCode(fgColor)
+ st = st.Foreground(c)
+ colorState = 0
+ } else if colorState == 4 {
+ if '0' <= r && r <= '9' {
+ bgColor = bgColor*10 + int(r-'0')
+ colorState = 5
+ continue
+ }
+
+ c := colorFromCode(fgColor)
+ st = st.Foreground(c)
+ colorState = 0
+
+ ui.screen.SetContent(x, y, ',', nil, st)
+ x++
+ } else if colorState == 5 {
+ if '0' <= r && r <= '9' {
+ bgColor = bgColor*10 + int(r-'0')
+ colorState = 6
+ continue
+ }
+
+ st = st.Foreground(colorFromCode(fgColor))
+ st = st.Background(colorFromCode(bgColor))
+ colorState = 0
+ } else if colorState == 6 {
+ st = st.Foreground(colorFromCode(fgColor))
+ st = st.Background(colorFromCode(bgColor))
+ colorState = 0
+ }
+
+ if r == 0x00 {
+ bold = false
+ italic = false
+ underline = false
+ st = st.Normal()
+ continue
+ }
+ if r == 0x02 {
+ bold = !bold
+ st = st.Bold(bold)
+ continue
+ }
+ if r == 0x03 {
+ colorState = 1
+ continue
+ }
+ if r == 0x1D {
+ italic = !italic
+ //st = st.Italic(italic)
+ continue
+ }
+ if r == 0x1F {
+ underline = !underline
+ //st = st.Underline(underline)
+ continue
+ }
+
+ ui.screen.SetContent(x, y, r, nil, st)
+ x += runewidth.RuneWidth(r)
+ }
+
+ y++
+ st = tcell.StyleDefault
+ bold = false
+ italic = false
+ underline = false
+ colorState = 0
+ if h-2 <= y {
+ break
+ }
+ }
+
+ ui.screen.Show()
+}
+
+func (ui *UI) drawStatus() {
+ st := tcell.StyleDefault
+ w, h := ui.screen.Size()
+
+ x := 0
+ y := h - 1
+
+ l := 0
+ start := 0
+ for _, b := range ui.bufferList.List {
+ if 0 < b.Highlights {
+ l += 3 // TODO
+ }
+
+ sw := runewidth.StringWidth(b.Title) + 1
+ l += sw
+ }
+
+ if w < l && ui.bufferList.Current > 0 {
+ start = ui.bufferList.Current - 1
+ }
+
+ for i := start; i < len(ui.bufferList.List); i++ {
+ b := ui.bufferList.List[i]
+
+ if i == ui.bufferList.Current {
+ st = st.Underline(true)
+ }
+ if 0 < b.Highlights {
+ st = st.Bold(true)
+ }
+
+ rs := []rune(b.Title)
+ for _, r := range rs {
+ if w <= x {
+ break
+ }
+
+ ui.screen.SetContent(x, y, r, nil, st)
+ x += runewidth.RuneWidth(r)
+ }
+
+ if w+1 <= x {
+ break
+ }
+
+ st = st.Normal()
+
+ ui.screen.SetContent(x, y, ' ', nil, st)
+ x++
+ }
+
+ for ; x < w; x++ {
+ ui.screen.SetContent(x, y, ' ', nil, st)
+ }
+
+ ui.screen.Show()
+}
+
+func colorFromCode(code int) (color tcell.Color) {
+ switch code {
+ case 0:
+ color = tcell.ColorWhite
+ case 1:
+ color = tcell.ColorBlack
+ case 2:
+ color = tcell.ColorBlue
+ case 3:
+ color = tcell.ColorGreen
+ case 4:
+ color = tcell.ColorRed
+ case 5:
+ color = tcell.ColorBrown
+ case 6:
+ color = tcell.ColorFuchsia
+ case 7:
+ color = tcell.ColorOrange
+ case 8:
+ color = tcell.ColorYellow
+ case 9:
+ color = tcell.ColorLime
+ case 10:
+ color = tcell.ColorAqua
+ case 11:
+ color = tcell.ColorLightCyan
+ case 12:
+ color = tcell.ColorLightBlue
+ case 13:
+ color = tcell.ColorPink
+ case 14:
+ color = tcell.ColorGrey
+ case 15:
+ color = tcell.ColorLightGrey
+ default:
+ color = tcell.Color(code)
+ }
+
+ return
+}