add style management

This commit is contained in:
David 2021-07-13 18:32:01 -04:00
parent 8c6c9325da
commit c2d2fe25c0
4 changed files with 98 additions and 39 deletions

View File

@ -1,4 +1,4 @@
.PHONY: up down run .PHONY: up down run-server run-manager test
GOFILES=$(shell find . -name '*.go' -o -name 'go.*') GOFILES=$(shell find . -name '*.go' -o -name 'go.*')
STATICFILES=$(shell find . -name '*.js' -o -name '*.css' -o -name '*.html') STATICFILES=$(shell find . -name '*.js' -o -name '*.css' -o -name '*.html')
@ -23,6 +23,9 @@ server: $(GOFILES) $(STATICFILES)
manager: $(GOFILES) $(SQLFILES) manager: $(GOFILES) $(SQLFILES)
go build -o manager ./cmd/manage go build -o manager ./cmd/manage
test:
go test ./... -cover
# dev dependencies # dev dependencies
up: up:
docker compose up -d docker compose up -d

View File

@ -125,6 +125,8 @@ func main() {
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2}, Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2},
Container: l, Container: l,
}}, }},
ui.StyleActive,
false,
) )
book := ui.NewBookDetails(&book.Book{ book := ui.NewBookDetails(&book.Book{
Title: "test title", Title: "test title",
@ -136,6 +138,8 @@ func main() {
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0}, Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0},
Container: book, Container: book,
}}, }},
ui.StyleInactive,
false,
) )
container := ui.NewContainer( container := ui.NewContainer(
@ -155,6 +159,7 @@ func main() {
// init UI state // init UI state
state.Set("ui_state", IN_MENU) state.Set("ui_state", IN_MENU)
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
// UI loop // UI loop
for { for {
@ -168,21 +173,26 @@ func main() {
if curr == IN_MENU { if curr == IN_MENU {
if v.Key() == tcell.KeyUp && l.Selected() > 0 { if v.Key() == tcell.KeyUp && l.Selected() > 0 {
l.SetSelected(l.Selected() - 1) l.SetSelected(l.Selected() - 1)
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
} }
if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 { if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 {
l.SetSelected(l.Selected() + 1) l.SetSelected(l.Selected() + 1)
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
} }
if v.Rune() == 'q' { if v.Rune() == 'q' {
screen.Fini() screen.Fini()
fmt.Printf("Thank you for playing Wing Commander!\n\n") fmt.Printf("Thank you for playing Wing Commander!\n\n")
return return
} }
if v.Key() == tcell.KeyRight { if v.Key() == tcell.KeyEnter {
screen.PostEvent(NewEventLoadBook(l.SelectedID())) activeBook.SetStyle(ui.StyleActive)
menu.SetStyle(ui.StyleInactive)
state.Set("ui_state", IN_BOOK) state.Set("ui_state", IN_BOOK)
} }
} else if curr == IN_BOOK { } else if curr == IN_BOOK {
if v.Key() == tcell.KeyLeft { if v.Key() == tcell.KeyEsc {
activeBook.SetStyle(ui.StyleInactive)
menu.SetStyle(ui.StyleActive)
state.Set("ui_state", IN_MENU) state.Set("ui_state", IN_MENU)
} }
} }

View File

@ -11,6 +11,7 @@ import (
type Drawable interface { type Drawable interface {
Draw(tcell.Screen) Draw(tcell.Screen)
SetSize(x, y, h, w int) SetSize(x, y, h, w int)
SetStyle(tcell.Style)
} }
type Offsets struct { type Offsets struct {
@ -34,6 +35,11 @@ const (
LayoutVerticalPercent 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. // 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 // 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 // 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 { func (c *Container) Contents() Contents {
return c.contents return c.contents
} }
@ -170,13 +180,17 @@ type Box struct {
title Drawable title Drawable
menuItems Drawable menuItems Drawable
contents Contents 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{ return &Box{
title: NewPaddedText(title), title: NewPaddedText(title),
menuItems: NewPaddedText(strings.Join(menuItems, " ")), menuItems: NewPaddedText(strings.Join(menuItems, " ")),
contents: contents, 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) { func (b *Box) Draw(s tcell.Screen) {
for m := b.x + 1; m < b.x+b.w-1; m++ { 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, tcell.RuneHLine, nil, b.style)
s.SetContent(m, b.y+b.h-1, tcell.RuneHLine, nil, tcell.StyleDefault) 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++ { 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, m, tcell.RuneVLine, nil, b.style)
s.SetContent(b.x+b.w-1, m, tcell.RuneVLine, nil, tcell.StyleDefault) 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.y, tcell.RuneULCorner, nil, b.style)
s.SetContent(b.x+b.w-1, b.y, tcell.RuneURCorner, nil, tcell.StyleDefault) 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, tcell.StyleDefault) 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, tcell.StyleDefault) s.SetContent(b.x+b.w-1, b.y+b.h-1, tcell.RuneLRCorner, nil, b.style)
if b.title != nil { if b.title != nil {
b.title.Draw(s) 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 { func (b *Box) Contents() Contents {
return b.contents return b.contents
} }
@ -232,6 +257,7 @@ type List struct {
h, w int h, w int
selected int selected int
listItems []ListKeyValue listItems []ListKeyValue
style tcell.Style
} }
type ListKeyValue struct { type ListKeyValue struct {
@ -253,14 +279,18 @@ func (l *List) SetSize(x, y, h, w int) {
func (l *List) Draw(s tcell.Screen) { func (l *List) Draw(s tcell.Screen) {
for i := range l.listItems { for i := range l.listItems {
for j, r := range l.listItems[i].Value { 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 { 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 { func (l *List) Selected() int {
return l.selected return l.selected
} }
@ -279,9 +309,10 @@ func (l *List) ListMembers() []ListKeyValue {
// BookDetails displays an editable list of book details // BookDetails displays an editable list of book details
type BookDetails struct { type BookDetails struct {
x, y int x, y int
h, w int h, w int
book *book.Book book *book.Book
style tcell.Style
} }
func NewBookDetails(b *book.Book) *BookDetails { func NewBookDetails(b *book.Book) *BookDetails {
@ -327,17 +358,23 @@ func (l *BookDetails) Draw(s tcell.Screen) {
if i < l.h-2 { if i < l.h-2 {
kv := NewKeyValue(items[i].label, ": ", items[i].value) kv := NewKeyValue(items[i].label, ": ", items[i].value)
kv.SetSize(l.x, l.y+i, 0, 0) kv.SetSize(l.x, l.y+i, 0, 0)
kv.SetStyle(l.style)
kv.Draw(s) kv.Draw(s)
} }
} }
} }
func (l *BookDetails) SetStyle(s tcell.Style) {
l.style = s
}
// PaddedText outputs strings with a space on both sides. // PaddedText outputs strings with a space on both sides.
// Useful for generating headings, footers, etc. Used by Box. // Useful for generating headings, footers, etc. Used by Box.
type PaddedText struct { type PaddedText struct {
x, y int x, y int
h, w int h, w int
text string text string
style tcell.Style
} }
func NewPaddedText(text string) *PaddedText { 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 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) { func (p *PaddedText) Draw(s tcell.Screen) {
if p.text == "" { if p.text == "" {
return return
} }
t := p.x t := p.x
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault) s.SetContent(t, p.y, ' ', nil, p.style)
t++ t++
for _, r := range p.text { for _, r := range p.text {
s.SetContent(t, p.y, r, nil, tcell.StyleDefault) s.SetContent(t, p.y, r, nil, p.style)
t++ t++
} }
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault) s.SetContent(t, p.y, ' ', nil, p.style)
} }
type KeyValue struct { type KeyValue struct {
@ -368,6 +409,7 @@ type KeyValue struct {
key string key string
value string value string
separator string separator string
style tcell.Style
} }
func NewKeyValue(key, separator, value string) *KeyValue { 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) 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) { func (p *KeyValue) Draw(s tcell.Screen) {
for j, r := range p.key { 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 { 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 { 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)
} }
} }

View File

@ -13,7 +13,7 @@ func TestContainerOneBox(t *testing.T) {
` `
m := &MockScreen{} m := &MockScreen{}
one := NewBox("box one", nil, Contents{}) one := NewBox("box one", nil, Contents{}, 0, false)
container := NewContainer( container := NewContainer(
Contents{{Container: one}}, Contents{{Container: one}},
LayoutHorizontalEven, LayoutHorizontalEven,
@ -38,8 +38,8 @@ func TestContainerTwoBoxesHStack(t *testing.T) {
` `
m := &MockScreen{} m := &MockScreen{}
one := NewBox("one", nil, Contents{}) one := NewBox("one", nil, Contents{}, 0, false)
two := NewBox("two", nil, Contents{}) two := NewBox("two", nil, Contents{}, 0, false)
container := NewContainer( container := NewContainer(
Contents{{Container: one}, {Container: two}}, Contents{{Container: one}, {Container: two}},
LayoutHorizontalEven, LayoutHorizontalEven,
@ -64,9 +64,9 @@ func TestContainerThreeBoxesUnevenHStack(t *testing.T) {
` `
m := &MockScreen{} m := &MockScreen{}
one := NewBox("one", nil, Contents{}) one := NewBox("one", nil, Contents{}, 0, false)
two := NewBox("two", nil, Contents{}) two := NewBox("two", nil, Contents{}, 0, false)
three := NewBox("three", nil, Contents{}) three := NewBox("three", nil, Contents{}, 0, false)
container := NewContainer( container := NewContainer(
Contents{{Container: one}, {Container: two}, {Container: three}}, Contents{{Container: one}, {Container: two}, {Container: three}},
LayoutHorizontalEven, LayoutHorizontalEven,
@ -91,8 +91,8 @@ func TestContainerTwoBoxesHPercentStack(t *testing.T) {
` `
m := &MockScreen{} m := &MockScreen{}
one := NewBox("one", nil, Contents{}) one := NewBox("one", nil, Contents{}, 0, false)
two := NewBox("two", nil, Contents{}) two := NewBox("two", nil, Contents{}, 0, false)
container := NewContainer( container := NewContainer(
Contents{ Contents{
{Container: one, Offsets: Offsets{Percent: 2}}, {Container: one, Offsets: Offsets{Percent: 2}},
@ -124,8 +124,8 @@ func TestContainerTwoBoxesVStack(t *testing.T) {
` `
m := &MockScreen{} m := &MockScreen{}
one := NewBox("one", nil, Contents{}) one := NewBox("one", nil, Contents{}, 0, false)
two := NewBox("two", nil, Contents{}) two := NewBox("two", nil, Contents{}, 0, false)
container := NewContainer( container := NewContainer(
Contents{{Container: one}, {Container: two}}, Contents{{Container: one}, {Container: two}},
LayoutVerticalEven, LayoutVerticalEven,
@ -155,8 +155,8 @@ func TestContainerTwoBoxesPercentageVStack(t *testing.T) {
` `
m := &MockScreen{} m := &MockScreen{}
one := NewBox("one", nil, Contents{}) one := NewBox("one", nil, Contents{}, 0, false)
two := NewBox("two", nil, Contents{}) two := NewBox("two", nil, Contents{}, 0, false)
container := NewContainer( container := NewContainer(
Contents{ Contents{
{Container: one, Offsets: Offsets{Percent: 2}}, {Container: one, Offsets: Offsets{Percent: 2}},