From c2d2fe25c0202439a3e619a22e349aacef01689c Mon Sep 17 00:00:00 2001 From: David Ashby Date: Tue, 13 Jul 2021 18:32:01 -0400 Subject: [PATCH] add style management --- Makefile | 5 ++- cmd/manage/main.go | 16 ++++++-- ui/ui.go | 92 ++++++++++++++++++++++++++++++++++------------ ui/ui_test.go | 24 ++++++------ 4 files changed, 98 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index 3529dfc..178b88d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: up down run +.PHONY: up down run-server run-manager test GOFILES=$(shell find . -name '*.go' -o -name 'go.*') STATICFILES=$(shell find . -name '*.js' -o -name '*.css' -o -name '*.html') @@ -23,6 +23,9 @@ server: $(GOFILES) $(STATICFILES) manager: $(GOFILES) $(SQLFILES) go build -o manager ./cmd/manage +test: + go test ./... -cover + # dev dependencies up: docker compose up -d diff --git a/cmd/manage/main.go b/cmd/manage/main.go index 49f4dd8..92a3edb 100644 --- a/cmd/manage/main.go +++ b/cmd/manage/main.go @@ -125,6 +125,8 @@ func main() { Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2}, Container: l, }}, + ui.StyleActive, + false, ) book := ui.NewBookDetails(&book.Book{ Title: "test title", @@ -136,6 +138,8 @@ func main() { Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0}, Container: book, }}, + ui.StyleInactive, + false, ) container := ui.NewContainer( @@ -155,6 +159,7 @@ func main() { // init UI state state.Set("ui_state", IN_MENU) + screen.PostEvent(NewEventLoadBook(l.SelectedID())) // UI loop for { @@ -168,21 +173,26 @@ func main() { if curr == IN_MENU { if v.Key() == tcell.KeyUp && l.Selected() > 0 { l.SetSelected(l.Selected() - 1) + screen.PostEvent(NewEventLoadBook(l.SelectedID())) } if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 { l.SetSelected(l.Selected() + 1) + screen.PostEvent(NewEventLoadBook(l.SelectedID())) } if v.Rune() == 'q' { screen.Fini() fmt.Printf("Thank you for playing Wing Commander!\n\n") return } - if v.Key() == tcell.KeyRight { - screen.PostEvent(NewEventLoadBook(l.SelectedID())) + if v.Key() == tcell.KeyEnter { + activeBook.SetStyle(ui.StyleActive) + menu.SetStyle(ui.StyleInactive) state.Set("ui_state", 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) } } diff --git a/ui/ui.go b/ui/ui.go index a3f87d6..b7d12b6 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -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) } } diff --git a/ui/ui_test.go b/ui/ui_test.go index caabc32..33452e4 100644 --- a/ui/ui_test.go +++ b/ui/ui_test.go @@ -13,7 +13,7 @@ func TestContainerOneBox(t *testing.T) { └──────────────────┘ ` m := &MockScreen{} - one := NewBox("box one", nil, Contents{}) + one := NewBox("box one", nil, Contents{}, 0, false) container := NewContainer( Contents{{Container: one}}, LayoutHorizontalEven, @@ -38,8 +38,8 @@ func TestContainerTwoBoxesHStack(t *testing.T) { └────────┘└────────┘ ` m := &MockScreen{} - one := NewBox("one", nil, Contents{}) - two := NewBox("two", nil, Contents{}) + one := NewBox("one", nil, Contents{}, 0, false) + two := NewBox("two", nil, Contents{}, 0, false) container := NewContainer( Contents{{Container: one}, {Container: two}}, LayoutHorizontalEven, @@ -64,9 +64,9 @@ func TestContainerThreeBoxesUnevenHStack(t *testing.T) { └────────┘└────────┘└───────┘ ` m := &MockScreen{} - one := NewBox("one", nil, Contents{}) - two := NewBox("two", nil, Contents{}) - three := NewBox("three", nil, Contents{}) + one := NewBox("one", nil, Contents{}, 0, false) + two := NewBox("two", nil, Contents{}, 0, false) + three := NewBox("three", nil, Contents{}, 0, false) container := NewContainer( Contents{{Container: one}, {Container: two}, {Container: three}}, LayoutHorizontalEven, @@ -91,8 +91,8 @@ func TestContainerTwoBoxesHPercentStack(t *testing.T) { └────────────┘└──────┘ ` m := &MockScreen{} - one := NewBox("one", nil, Contents{}) - two := NewBox("two", nil, Contents{}) + one := NewBox("one", nil, Contents{}, 0, false) + two := NewBox("two", nil, Contents{}, 0, false) container := NewContainer( Contents{ {Container: one, Offsets: Offsets{Percent: 2}}, @@ -124,8 +124,8 @@ func TestContainerTwoBoxesVStack(t *testing.T) { └────────┘ ` m := &MockScreen{} - one := NewBox("one", nil, Contents{}) - two := NewBox("two", nil, Contents{}) + one := NewBox("one", nil, Contents{}, 0, false) + two := NewBox("two", nil, Contents{}, 0, false) container := NewContainer( Contents{{Container: one}, {Container: two}}, LayoutVerticalEven, @@ -155,8 +155,8 @@ func TestContainerTwoBoxesPercentageVStack(t *testing.T) { └────────┘ ` m := &MockScreen{} - one := NewBox("one", nil, Contents{}) - two := NewBox("two", nil, Contents{}) + one := NewBox("one", nil, Contents{}, 0, false) + two := NewBox("two", nil, Contents{}, 0, false) container := NewContainer( Contents{ {Container: one, Offsets: Offsets{Percent: 2}},