|
|
|
@ -11,6 +11,7 @@ import (
|
|
|
|
|
type Drawable interface { |
|
|
|
|
Draw(tcell.Screen) |
|
|
|
|
SetSize(x, y, h, w int) |
|
|
|
|
SetStyle(tcell.Style) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Offsets struct { |
|
|
|
@ -34,6 +35,11 @@ const (
|
|
|
|
|
LayoutVerticalPercent |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
StyleActive = tcell.Style(0).Foreground(tcell.ColorWhite).Background(tcell.ColorBlack) |
|
|
|
|
StyleInactive = tcell.Style(0).Foreground(tcell.ColorGray).Background(tcell.ColorBlack) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// A Container has no visible UI of its own, but arranges sub-components on the screen.
|
|
|
|
|
// layoutMethod manages how subcomponents are organized. If `LayoutUnmanaged`, it just uses the offsets
|
|
|
|
|
// in contents to paint on the screen. Otherwise, `LayoutHorizontalEven` and `LayoutVerticalEven` will
|
|
|
|
@ -155,6 +161,10 @@ func (c *Container) SetSize(x, y, h, w int) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *Container) SetStyle(s tcell.Style) { |
|
|
|
|
// containers have no visible elements to style
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *Container) Contents() Contents { |
|
|
|
|
return c.contents |
|
|
|
|
} |
|
|
|
@ -170,13 +180,17 @@ type Box struct {
|
|
|
|
|
title Drawable |
|
|
|
|
menuItems Drawable |
|
|
|
|
contents Contents |
|
|
|
|
style tcell.Style |
|
|
|
|
cascade bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewBox(title string, menuItems []string, contents Contents) *Box { |
|
|
|
|
func NewBox(title string, menuItems []string, contents Contents, initialStyle tcell.Style, cascade bool) *Box { |
|
|
|
|
return &Box{ |
|
|
|
|
title: NewPaddedText(title), |
|
|
|
|
menuItems: NewPaddedText(strings.Join(menuItems, " ")), |
|
|
|
|
contents: contents, |
|
|
|
|
style: initialStyle, |
|
|
|
|
cascade: cascade, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -195,17 +209,17 @@ func (b *Box) SetSize(x, y, h, w int) {
|
|
|
|
|
|
|
|
|
|
func (b *Box) Draw(s tcell.Screen) { |
|
|
|
|
for m := b.x + 1; m < b.x+b.w-1; m++ { |
|
|
|
|
s.SetContent(m, b.y, tcell.RuneHLine, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(m, b.y+b.h-1, tcell.RuneHLine, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(m, b.y, tcell.RuneHLine, nil, b.style) |
|
|
|
|
s.SetContent(m, b.y+b.h-1, tcell.RuneHLine, nil, b.style) |
|
|
|
|
} |
|
|
|
|
for m := b.y + 1; m < b.y+b.h-1; m++ { |
|
|
|
|
s.SetContent(b.x, m, tcell.RuneVLine, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(b.x+b.w-1, m, tcell.RuneVLine, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(b.x, m, tcell.RuneVLine, nil, b.style) |
|
|
|
|
s.SetContent(b.x+b.w-1, m, tcell.RuneVLine, nil, b.style) |
|
|
|
|
} |
|
|
|
|
s.SetContent(b.x, b.y, tcell.RuneULCorner, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(b.x+b.w-1, b.y, tcell.RuneURCorner, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(b.x, b.y+b.h-1, tcell.RuneLLCorner, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(b.x+b.w-1, b.y+b.h-1, tcell.RuneLRCorner, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(b.x, b.y, tcell.RuneULCorner, nil, b.style) |
|
|
|
|
s.SetContent(b.x+b.w-1, b.y, tcell.RuneURCorner, nil, b.style) |
|
|
|
|
s.SetContent(b.x, b.y+b.h-1, tcell.RuneLLCorner, nil, b.style) |
|
|
|
|
s.SetContent(b.x+b.w-1, b.y+b.h-1, tcell.RuneLRCorner, nil, b.style) |
|
|
|
|
|
|
|
|
|
if b.title != nil { |
|
|
|
|
b.title.Draw(s) |
|
|
|
@ -218,6 +232,17 @@ func (b *Box) Draw(s tcell.Screen) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *Box) SetStyle(s tcell.Style) { |
|
|
|
|
b.style = s |
|
|
|
|
b.title.SetStyle(s) |
|
|
|
|
b.menuItems.SetStyle(s) |
|
|
|
|
if b.cascade { |
|
|
|
|
for c := range b.contents { |
|
|
|
|
b.contents[c].Container.SetStyle(s) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *Box) Contents() Contents { |
|
|
|
|
return b.contents |
|
|
|
|
} |
|
|
|
@ -232,6 +257,7 @@ type List struct {
|
|
|
|
|
h, w int |
|
|
|
|
selected int |
|
|
|
|
listItems []ListKeyValue |
|
|
|
|
style tcell.Style |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type ListKeyValue struct { |
|
|
|
@ -253,14 +279,18 @@ func (l *List) SetSize(x, y, h, w int) {
|
|
|
|
|
func (l *List) Draw(s tcell.Screen) { |
|
|
|
|
for i := range l.listItems { |
|
|
|
|
for j, r := range l.listItems[i].Value { |
|
|
|
|
s.SetContent(l.x+j, l.y+i, r, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(l.x+j, l.y+i, r, nil, l.style) |
|
|
|
|
} |
|
|
|
|
if i == l.selected { |
|
|
|
|
s.SetContent(l.x+len(l.listItems[i].Value)+1, l.y+i, '<', nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(l.x+len(l.listItems[i].Value)+1, l.y+i, '<', nil, l.style) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (l *List) SetStyle(s tcell.Style) { |
|
|
|
|
l.style = s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (l *List) Selected() int { |
|
|
|
|
return l.selected |
|
|
|
|
} |
|
|
|
@ -279,9 +309,10 @@ func (l *List) ListMembers() []ListKeyValue {
|
|
|
|
|
|
|
|
|
|
// BookDetails displays an editable list of book details
|
|
|
|
|
type BookDetails struct { |
|
|
|
|
x, y int |
|
|
|
|
h, w int |
|
|
|
|
book *book.Book |
|
|
|
|
x, y int |
|
|
|
|
h, w int |
|
|
|
|
book *book.Book |
|
|
|
|
style tcell.Style |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewBookDetails(b *book.Book) *BookDetails { |
|
|
|
@ -327,17 +358,23 @@ func (l *BookDetails) Draw(s tcell.Screen) {
|
|
|
|
|
if i < l.h-2 { |
|
|
|
|
kv := NewKeyValue(items[i].label, ": ", items[i].value) |
|
|
|
|
kv.SetSize(l.x, l.y+i, 0, 0) |
|
|
|
|
kv.SetStyle(l.style) |
|
|
|
|
kv.Draw(s) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (l *BookDetails) SetStyle(s tcell.Style) { |
|
|
|
|
l.style = s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// PaddedText outputs strings with a space on both sides.
|
|
|
|
|
// Useful for generating headings, footers, etc. Used by Box.
|
|
|
|
|
type PaddedText struct { |
|
|
|
|
x, y int |
|
|
|
|
h, w int |
|
|
|
|
text string |
|
|
|
|
x, y int |
|
|
|
|
h, w int |
|
|
|
|
text string |
|
|
|
|
style tcell.Style |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewPaddedText(text string) *PaddedText { |
|
|
|
@ -348,18 +385,22 @@ func (p *PaddedText) SetSize(x, y, _, _ int) {
|
|
|
|
|
p.x, p.y, p.h, p.w = x, y, 1, len(p.text)+2 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *PaddedText) SetStyle(s tcell.Style) { |
|
|
|
|
p.style = s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *PaddedText) Draw(s tcell.Screen) { |
|
|
|
|
if p.text == "" { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
t := p.x |
|
|
|
|
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(t, p.y, ' ', nil, p.style) |
|
|
|
|
t++ |
|
|
|
|
for _, r := range p.text { |
|
|
|
|
s.SetContent(t, p.y, r, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(t, p.y, r, nil, p.style) |
|
|
|
|
t++ |
|
|
|
|
} |
|
|
|
|
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(t, p.y, ' ', nil, p.style) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type KeyValue struct { |
|
|
|
@ -368,6 +409,7 @@ type KeyValue struct {
|
|
|
|
|
key string |
|
|
|
|
value string |
|
|
|
|
separator string |
|
|
|
|
style tcell.Style |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewKeyValue(key, separator, value string) *KeyValue { |
|
|
|
@ -382,15 +424,19 @@ func (p *KeyValue) SetSize(x, y, _, _ int) {
|
|
|
|
|
p.x, p.y, p.h, p.w = x, y, 1, len(p.key)+len(p.separator)+len(p.value) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *KeyValue) SetStyle(s tcell.Style) { |
|
|
|
|
p.style = s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *KeyValue) Draw(s tcell.Screen) { |
|
|
|
|
for j, r := range p.key { |
|
|
|
|
s.SetContent(p.x+j, p.y, r, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(p.x+j, p.y, r, nil, p.style) |
|
|
|
|
} |
|
|
|
|
for j, r := range p.separator { |
|
|
|
|
s.SetContent(p.x+len(p.key)+j, p.y, r, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(p.x+len(p.key)+j, p.y, r, nil, p.style) |
|
|
|
|
} |
|
|
|
|
for j, r := range p.value { |
|
|
|
|
s.SetContent(p.x+len(p.key)+len(p.separator)+j, p.y, r, nil, tcell.StyleDefault) |
|
|
|
|
s.SetContent(p.x+len(p.key)+len(p.separator)+j, p.y, r, nil, p.style) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|