-
Notifications
You must be signed in to change notification settings - Fork 23
/
topbar.go
150 lines (130 loc) · 3.54 KB
/
topbar.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package widgets
import (
"github.com/ambientsound/pms/console"
"github.com/ambientsound/pms/style"
"github.com/ambientsound/pms/topbar"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
`github.com/mattn/go-runewidth`
)
// Pieces may be aligned to left, center or right.
const (
AlignLeft = iota
AlignCenter
AlignRight
)
// Topbar is a widget that can display a variety of information, such as the
// currently playing song. It is composed of several pieces to form a
// two-dimensional matrix.
type Topbar struct {
matrix *topbar.MatrixStatement
height int // height is both physical and matrix height
view views.View
style.Styled
views.WidgetWatchers
}
// NewTopbar creates a new Topbar widget in the desired dimensions.
func NewTopbar() *Topbar {
return &Topbar{
height: 0,
matrix: &topbar.MatrixStatement{},
}
}
// Setup sets up the topbar using the provided configuration string.
func (w *Topbar) SetMatrix(matrix *topbar.MatrixStatement) {
w.matrix = matrix
w.height = len(matrix.Rows)
console.Log("Setting up new topbar with height %d", w.height)
}
// Draw draws all the pieces in the matrix, from top to bottom, right to left.
func (w *Topbar) Draw() {
xmax, _ := w.Size()
// Blank screen first
w.view.Fill(' ', w.Style("topbar"))
for y, rowStmt := range w.matrix.Rows {
// Calculate window buffer width
pieces := len(rowStmt.Pieces)
if pieces == 0 {
continue
}
for piece, pieceStmt := range rowStmt.Pieces {
// Reset X position to start of window buffer, and align left,
// center or right.
align := autoAlign(piece, pieces)
textWidth := pieceTextWidth(pieceStmt)
x := getPiecesStartX(piece, pieces, xmax)
x2 := getPiecesStartX(piece+1, pieces, xmax)
x = alignX(x, x2-x, textWidth, align)
for _, fragmentStmt := range pieceStmt.Fragments {
frag := fragmentStmt.Instance
text, styleStr := frag.Text()
style := w.Style(styleStr)
x = w.drawNext(x, y, text, style)
}
}
}
}
// drawNext draws a string and returns the resulting X position.
func (w *Topbar) drawNext(x, y int, s string, style tcell.Style) int {
for _, r := range s {
w.view.SetContent(x, y, r, nil, style)
x += runewidth.RuneWidth(r)
}
return x
}
// autoAlign returns a best-guess align for a Piece: the outermost indices are
// left- and right adjusted, while the rest are centered.
func autoAlign(index, total int) int {
switch index {
case 0:
return AlignLeft
case total - 1:
return AlignRight
default:
return AlignCenter
}
}
// getPiecesStartX calculates the start x-position for a given piece.
//
// Unused space is avoided by assigning extra space to the first pieces,
// if (xmax / pieces) leaves a remainder.
func getPiecesStartX(piece, pieces, xmax int) int {
x := piece * (xmax / pieces)
if piece <= (xmax % pieces) {
return x + piece
}
return x + (xmax % pieces)
}
// alignX returns the draw start position.
func alignX(x, bufferWidth, textWidth, align int) int {
switch align {
case AlignLeft:
return x
case AlignCenter:
return x + (bufferWidth / 2) - (textWidth / 2)
case AlignRight:
return x + bufferWidth - textWidth
default:
return x
}
}
func pieceTextWidth(piece *topbar.PieceStatement) int {
width := 0
for _, fragment := range piece.Fragments {
s, _ := fragment.Instance.Text()
width += runewidth.StringWidth(s)
}
return width
}
func (w *Topbar) HandleEvent(ev tcell.Event) bool {
return false
}
func (w *Topbar) Size() (int, int) {
x, _ := w.view.Size()
return x, w.height
}
func (w *Topbar) Resize() {
}
func (w *Topbar) SetView(v views.View) {
w.view = v
}