add style management
This commit is contained in:
parent
8c6c9325da
commit
c2d2fe25c0
5
Makefile
5
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.*')
|
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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
ui/ui.go
92
ui/ui.go
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}},
|
||||||
|
Loading…
Reference in New Issue
Block a user