summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
authorHubert Hirtz <hubert@hirtz.pm>2020-10-21 16:22:28 +0200
committerHubert Hirtz <hubert@hirtz.pm>2020-10-26 08:45:07 +0100
commit7fae7f74c5315877e0d341ec60ceef2e87c0c1cc (patch)
treeafdea6e39840dfbd3db4e864bf914fe137009298 /ui
parentCollapse bufferlist in one block (diff)
Vertical channel list
Diffstat (limited to 'ui')
-rw-r--r--ui/buffers.go264
-rw-r--r--ui/draw_utils.go40
-rw-r--r--ui/editor.go10
-rw-r--r--ui/style.go19
-rw-r--r--ui/ui.go50
5 files changed, 195 insertions, 188 deletions
diff --git a/ui/buffers.go b/ui/buffers.go
index 1347635..173264b 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -1,8 +1,6 @@
package ui
import (
- "fmt"
- "math"
"strings"
"time"
@@ -169,101 +167,31 @@ type buffer struct {
isAtTop bool
}
-func (b *buffer) DrawLines(screen tcell.Screen, x0, width, height, nickColWidth int) {
- st := tcell.StyleDefault
- for x := 0; x < width; x++ {
- for y := 0; y < height; y++ {
- screen.SetContent(x, y, ' ', nil, st)
- }
- }
-
- y0 := b.scrollAmt + height
- for i := len(b.lines) - 1; 0 <= i; i-- {
- if y0 < 0 {
- break
- }
-
- x1 := x0 + 9 + nickColWidth
-
- line := &b.lines[i]
- nls := line.NewLines(width - x1)
- y0 -= len(nls) + 1
- if height <= y0 {
- continue
- }
-
- if i == 0 || b.lines[i-1].At.Truncate(time.Minute) != line.At.Truncate(time.Minute) {
- printTime(screen, x0, y0, st.Bold(true), line.At.Local())
- }
-
- head := truncate(line.Head, nickColWidth, "\u2026")
- x := x0 + 7 + nickColWidth - StringWidth(head)
- st = st.Foreground(colorFromCode(line.HeadColor))
- if line.Highlight {
- st = st.Reverse(true)
- }
- screen.SetContent(x-1, y0, ' ', nil, st)
- screen.SetContent(x0+7+nickColWidth, y0, ' ', nil, st)
- printString(screen, &x, y0, st, head)
- st = st.Reverse(false).Foreground(tcell.ColorDefault)
-
- x = x1
- y := y0
-
- var sb StyleBuffer
- sb.Reset()
- for i, r := range line.Body {
- if 0 < len(nls) && i == nls[0] {
- x = x1
- y++
- nls = nls[1:]
- if height < y {
- break
- }
- }
-
- if y != y0 && x == x1 && IsSplitRune(r) {
- continue
- }
-
- if st, ok := sb.WriteRune(r); ok != 0 {
- if 1 < ok {
- screen.SetContent(x, y, ',', nil, st)
- x++
- }
- screen.SetContent(x, y, r, nil, st)
- x += runeWidth(r)
- }
- }
-
- sb.Reset()
- }
-
- b.isAtTop = 0 <= y0
-}
-
type BufferList struct {
list []buffer
current int
- status string
- width int
- height int
+ tlWidth int
+ tlHeight int
nickColWidth int
}
-func NewBufferList(width, height, nickColWidth int) BufferList {
+func NewBufferList(tlWidth, tlHeight, nickColWidth int) BufferList {
return BufferList{
list: []buffer{},
- width: width,
- height: height,
+ tlWidth: tlWidth,
+ tlHeight: tlHeight,
nickColWidth: nickColWidth,
}
}
-func (bs *BufferList) Resize(width, height int) {
- bs.width = width
- bs.height = height
+func (bs *BufferList) ResizeTimeline(tlWidth, tlHeight, nickColWidth int) {
+ bs.tlWidth = tlWidth
+ bs.tlHeight = tlHeight
+}
+
+func (bs *BufferList) tlInnerWidth() int {
+ return bs.tlWidth - bs.nickColWidth - 9
}
func (bs *BufferList) Next() {
@@ -326,7 +254,7 @@ func (bs *BufferList) AddLine(title string, highlight bool, line Line) {
line.computeSplitPoints()
b.lines = append(b.lines, line)
if idx == bs.current && 0 < b.scrollAmt {
- b.scrollAmt += len(line.NewLines(bs.width-9-bs.nickColWidth)) + 1
+ b.scrollAmt += len(line.NewLines(bs.tlInnerWidth())) + 1
}
}
@@ -363,10 +291,6 @@ func (bs *BufferList) AddLines(title string, lines []Line) {
b.lines = append(lines[:limit], b.lines...)
}
-func (bs *BufferList) SetStatus(status string) {
- bs.status = status
-}
-
func (bs *BufferList) Current() (title string) {
return bs.list[bs.current].title
}
@@ -384,12 +308,12 @@ func (bs *BufferList) ScrollUp() {
if b.isAtTop {
return
}
- b.scrollAmt += bs.height / 2
+ b.scrollAmt += bs.tlHeight / 2
}
func (bs *BufferList) ScrollDown() {
b := &bs.list[bs.current]
- b.scrollAmt -= bs.height / 2
+ b.scrollAmt -= bs.tlHeight / 2
if b.scrollAmt < 0 {
b.scrollAmt = 0
@@ -415,79 +339,28 @@ func (bs *BufferList) idx(title string) int {
return -1
}
-func (bs *BufferList) Draw(screen tcell.Screen) {
- bs.list[bs.current].DrawLines(screen, 0, bs.width, bs.height-2, bs.nickColWidth)
- bs.drawStatusBar(screen, bs.height-2)
- bs.drawTitleList(screen, bs.height-1)
-}
-
-func (bs *BufferList) drawStatusBar(screen tcell.Screen, y int) {
- st := tcell.StyleDefault.Dim(true)
-
- for x := 0; x < bs.width; x++ {
- screen.SetContent(x, y, 0x2500, nil, st)
- }
-
- if bs.status == "" {
- return
- }
-
- x := 2
- screen.SetContent(1, y, 0x2524, nil, st)
- printString(screen, &x, y, st, bs.status)
- screen.SetContent(x, y, 0x251c, nil, st)
-}
-
-func (bs *BufferList) drawTitleList(screen tcell.Screen, y int) {
- var widths []int
- for _, b := range bs.list {
- width := StringWidth(b.title)
- if 0 < b.highlights {
- width += int(math.Log10(float64(b.highlights))) + 3
- }
- widths = append(widths, width)
- }
-
+func (bs *BufferList) DrawVerticalBufferList(screen tcell.Screen, x0, y0, width, height int) {
+ width--
st := tcell.StyleDefault
- for x := 0; x < bs.width; x++ {
- screen.SetContent(x, y, ' ', nil, st)
- }
-
- x := (bs.width - widths[bs.current]) / 2
- printString(screen, &x, y, st.Underline(true), bs.list[bs.current].title)
- x += 2
-
- i := (bs.current + 1) % len(bs.list)
- for x < bs.width && i != bs.current {
- b := &bs.list[i]
- st = tcell.StyleDefault
- if b.unread {
- st = st.Bold(true)
- }
- printString(screen, &x, y, st, b.title)
- if 0 < b.highlights {
- st = st.Foreground(tcell.ColorRed).Reverse(true)
+ for y := y0; y < y0+height; y++ {
+ for x := x0; x < x0+width; x++ {
screen.SetContent(x, y, ' ', nil, st)
- x++
- printNumber(screen, &x, y, st, b.highlights)
- screen.SetContent(x, y, ' ', nil, st)
- x++
}
- x += 2
- i = (i + 1) % len(bs.list)
+ screen.SetContent(x0+width, y, 0x2502, nil, st.Dim(true))
}
- i = (bs.current - 1 + len(bs.list)) % len(bs.list)
- x = (bs.width - widths[bs.current]) / 2
- for 0 < x && i != bs.current {
- x -= widths[i] + 2
- b := &bs.list[i]
+ for i, b := range bs.list {
st = tcell.StyleDefault
+ x := x0
+ y := y0 + i
if b.unread {
st = st.Bold(true)
+ } else if y == bs.current {
+ st = st.Underline(true)
}
- printString(screen, &x, y, st, b.title)
+ title := truncate(b.title, width, "\u2026")
+ printString(screen, &x, y, st, title)
if 0 < b.highlights {
st = st.Foreground(tcell.ColorRed).Reverse(true)
screen.SetContent(x, y, ' ', nil, st)
@@ -496,33 +369,74 @@ func (bs *BufferList) drawTitleList(screen tcell.Screen, y int) {
screen.SetContent(x, y, ' ', nil, st)
x++
}
- x -= widths[i]
- i = (i - 1 + len(bs.list)) % len(bs.list)
+ y++
}
}
-func printString(screen tcell.Screen, x *int, y int, st tcell.Style, s string) {
- for _, r := range s {
- screen.SetContent(*x, y, r, nil, st)
- *x += runeWidth(r)
+func (bs *BufferList) DrawTimeline(screen tcell.Screen, x0, y0, nickColWidth int) {
+ st := tcell.StyleDefault
+ for x := x0; x < x0+bs.tlWidth; x++ {
+ for y := y0; y < y0+bs.tlHeight; y++ {
+ screen.SetContent(x, y, ' ', nil, st)
+ }
}
-}
-func printNumber(screen tcell.Screen, x *int, y int, st tcell.Style, n int) {
- s := fmt.Sprintf("%d", n)
- printString(screen, x, y, st, s)
-}
+ b := &bs.list[bs.current]
+ yi := b.scrollAmt + y0 + bs.tlHeight
+ for i := len(b.lines) - 1; 0 <= i; i-- {
+ if yi < 0 {
+ break
+ }
+
+ x1 := x0 + 9 + nickColWidth
+
+ line := &b.lines[i]
+ nls := line.NewLines(bs.tlInnerWidth())
+ yi -= len(nls) + 1
+ if y0+bs.tlHeight <= yi {
+ continue
+ }
+
+ if i == 0 || b.lines[i-1].At.Truncate(time.Minute) != line.At.Truncate(time.Minute) {
+ printTime(screen, x0, yi, st.Bold(true), line.At.Local())
+ }
+
+ identSt := st.Foreground(colorFromCode(line.HeadColor)).Reverse(line.Highlight)
+ printIdent(screen, x0+7, yi, nickColWidth, identSt, line.Head)
+
+ x := x1
+ y := yi
+
+ var sb StyleBuffer
+ sb.Reset()
+ for i, r := range line.Body {
+ if 0 < len(nls) && i == nls[0] {
+ x = x1
+ y++
+ nls = nls[1:]
+ if bs.tlHeight < y {
+ break
+ }
+ }
+
+ if y != yi && x == x1 && IsSplitRune(r) {
+ continue
+ }
+
+ if st, ok := sb.WriteRune(r); ok != 0 {
+ if 1 < ok {
+ screen.SetContent(x, y, ',', nil, st)
+ x++
+ }
+ screen.SetContent(x, y, r, nil, st)
+ x += runeWidth(r)
+ }
+ }
+
+ sb.Reset()
+ }
-func printTime(screen tcell.Screen, x int, y int, st tcell.Style, t time.Time) {
- hr0 := rune(t.Hour()/10) + '0'
- hr1 := rune(t.Hour()%10) + '0'
- mn0 := rune(t.Minute()/10) + '0'
- mn1 := rune(t.Minute()%10) + '0'
- screen.SetContent(x+0, y, hr0, nil, st)
- screen.SetContent(x+1, y, hr1, nil, st)
- screen.SetContent(x+2, y, ':', nil, st)
- screen.SetContent(x+3, y, mn0, nil, st)
- screen.SetContent(x+4, y, mn1, nil, st)
+ b.isAtTop = y0 <= yi
}
func IrcColorCode(code int) string {
diff --git a/ui/draw_utils.go b/ui/draw_utils.go
new file mode 100644
index 0000000..893a77f
--- /dev/null
+++ b/ui/draw_utils.go
@@ -0,0 +1,40 @@
+package ui
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/gdamore/tcell/v2"
+)
+
+func printIdent(screen tcell.Screen, x, y, width int, st tcell.Style, s string) {
+ s = truncate(s, width, "\u2026")
+ x += width - StringWidth(s)
+ screen.SetContent(x-1, y, ' ', nil, st)
+ printString(screen, &x, y, st, s)
+ screen.SetContent(x, y, ' ', nil, st)
+}
+
+func printString(screen tcell.Screen, x *int, y int, st tcell.Style, s string) {
+ for _, r := range s {
+ screen.SetContent(*x, y, r, nil, st)
+ *x += runeWidth(r)
+ }
+}
+
+func printNumber(screen tcell.Screen, x *int, y int, st tcell.Style, n int) {
+ s := fmt.Sprintf("%d", n)
+ printString(screen, x, y, st, s)
+}
+
+func printTime(screen tcell.Screen, x int, y int, st tcell.Style, t time.Time) {
+ hr0 := rune(t.Hour()/10) + '0'
+ hr1 := rune(t.Hour()%10) + '0'
+ mn0 := rune(t.Minute()/10) + '0'
+ mn1 := rune(t.Minute()%10) + '0'
+ screen.SetContent(x+0, y, hr0, nil, st)
+ screen.SetContent(x+1, y, hr1, nil, st)
+ screen.SetContent(x+2, y, ':', nil, st)
+ screen.SetContent(x+3, y, mn0, nil, st)
+ screen.SetContent(x+4, y, mn1, nil, st)
+}
diff --git a/ui/editor.go b/ui/editor.go
index cfc26b5..8632e81 100644
--- a/ui/editor.go
+++ b/ui/editor.go
@@ -242,20 +242,20 @@ func (e *Editor) computeTextWidth() {
}
}
-func (e *Editor) Draw(screen tcell.Screen, y int) {
+func (e *Editor) Draw(screen tcell.Screen, x0, y int) {
st := tcell.StyleDefault
- x := 0
+ x := x0
i := e.offsetIdx
- for i < len(e.text[e.lineIdx]) && x < e.width {
+ for i < len(e.text[e.lineIdx]) && x < x0+e.width {
r := e.text[e.lineIdx][i]
screen.SetContent(x, y, r, nil, st)
x += runeWidth(r)
i++
}
- for x < e.width {
+ for x < x0+e.width {
screen.SetContent(x, y, ' ', nil, st)
x++
}
@@ -265,7 +265,7 @@ func (e *Editor) Draw(screen tcell.Screen, y int) {
if e.cursorIdx+1 < len(e.textWidth) {
curEnd = e.textWidth[e.cursorIdx+1] - e.textWidth[e.offsetIdx]
}
- for x := curStart; x < curEnd; x++ {
+ for x := x0 + curStart; x < x0+curEnd; x++ {
screen.ShowCursor(x, y)
}
}
diff --git a/ui/style.go b/ui/style.go
index 77dc515..acc1a41 100644
--- a/ui/style.go
+++ b/ui/style.go
@@ -1,6 +1,8 @@
package ui
import (
+ "hash/fnv"
+
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
)
@@ -223,3 +225,20 @@ func colorFromCode(code int) (color tcell.Color) {
}
return
}
+
+// see <https://modern.ircdocs.horse/formatting.html>
+var identColorBlacklist = []int{1, 8, 16, 27, 28, 88, 89, 90, 91}
+
+func IdentColor(s string) (code int) {
+ h := fnv.New32()
+ _, _ = h.Write([]byte(s))
+
+ code = int(h.Sum32()) % (99 - len(identColorBlacklist))
+ for _, c := range identColorBlacklist {
+ if c <= code {
+ code++
+ }
+ }
+
+ return
+}
diff --git a/ui/ui.go b/ui/ui.go
index a85aed7..3acbe1e 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -18,8 +18,10 @@ type UI struct {
exit atomic.Value // bool
config Config
- bs BufferList
- e Editor
+ bs BufferList
+ e Editor
+ prompt string
+ status string
}
func New(config Config) (ui *UI, err error) {
@@ -115,7 +117,11 @@ func (ui *UI) AddLines(buffer string, lines []Line) {
}
func (ui *UI) SetStatus(status string) {
- ui.bs.SetStatus(status)
+ ui.status = status
+}
+
+func (ui *UI) SetPrompt(prompt string) {
+ ui.prompt = prompt
}
func (ui *UI) InputIsCommand() bool {
@@ -172,13 +178,41 @@ func (ui *UI) InputEnter() (content string) {
func (ui *UI) Resize() {
w, h := ui.screen.Size()
- ui.e.Resize(w)
- ui.bs.Resize(w, h-1)
+ ui.e.Resize(w - 25 - ui.config.NickColWidth)
+ ui.bs.ResizeTimeline(w-16, h-2, ui.config.NickColWidth)
}
func (ui *UI) Draw() {
- _, h := ui.screen.Size()
- ui.e.Draw(ui.screen, h-1)
- ui.bs.Draw(ui.screen)
+ w, h := ui.screen.Size()
+
+ ui.e.Draw(ui.screen, 25+ui.config.NickColWidth, h-1)
+
+ ui.bs.DrawTimeline(ui.screen, 16, 0, ui.config.NickColWidth)
+ ui.bs.DrawVerticalBufferList(ui.screen, 0, 0, 16, h)
+ ui.drawStatusBar(16, h-2, w-16)
+
+ for x := 16; x < 25+ui.config.NickColWidth; x++ {
+ ui.screen.SetContent(x, h-1, ' ', nil, tcell.StyleDefault)
+ }
+ st := tcell.StyleDefault.Foreground(colorFromCode(IdentColor(ui.prompt)))
+ printIdent(ui.screen, 23, h-1, ui.config.NickColWidth, st, ui.prompt)
+
ui.screen.Show()
}
+
+func (ui *UI) drawStatusBar(x0, y, width int) {
+ st := tcell.StyleDefault.Dim(true)
+
+ for x := x0; x < x0+width; x++ {
+ ui.screen.SetContent(x, y, ' ', nil, st)
+ }
+
+ if ui.status == "" {
+ return
+ }
+
+ x := x0 + 5 + ui.config.NickColWidth
+ printString(ui.screen, &x, y, st, "--")
+ x += 2
+ printString(ui.screen, &x, y, st, ui.status)
+}