doesn't quite work yet

This commit is contained in:
David 2021-07-03 13:30:08 -04:00
parent de3f9cfadb
commit 6cd94df521
3 changed files with 252 additions and 150 deletions

View File

@ -1,135 +0,0 @@
package main
import (
"strings"
"github.com/gdamore/tcell"
)
type Drawable interface {
Draw(tcell.Screen)
SetSize(x, y, h, w int)
}
type offsets struct {
top int
bottom int
left int
right int
}
type contents []struct {
offsets offsets
container Drawable
}
type box struct {
x, y int
h, w int
title Drawable
menuItems Drawable
contents []struct {
offsets offsets
container Drawable
}
}
func NewBox(title string, menuItems []string, contents contents) *box {
return &box{
title: NewPaddedText(title),
menuItems: NewPaddedText(strings.Join(menuItems, " ")),
contents: contents,
}
}
func (b *box) SetSize(x, y, h, w int) {
b.x, b.y, b.h, b.w = x, y, h, w
b.title.SetSize(b.x+2, b.y, 0, 0)
b.menuItems.SetSize(b.x+2, b.y+b.h-1, 0, 0)
for c := range b.contents {
x := b.x + b.contents[c].offsets.left
y := b.y + b.contents[c].offsets.top
h := b.h - b.contents[c].offsets.bottom
w := b.w - b.contents[c].offsets.right
b.contents[c].container.SetSize(x, y, h, w)
}
}
func (b *box) Draw(s tcell.Screen) {
for m := 1; m < b.w-1; m++ {
s.SetContent(m, b.y, tcell.RuneHLine, nil, tcell.StyleDefault)
s.SetContent(m, b.h-1, tcell.RuneHLine, nil, tcell.StyleDefault)
}
for m := 1; m < b.h-1; m++ {
s.SetContent(b.x, m, tcell.RuneVLine, nil, tcell.StyleDefault)
s.SetContent(b.w-1, m, tcell.RuneVLine, nil, tcell.StyleDefault)
}
s.SetContent(b.x, b.y, tcell.RuneULCorner, nil, tcell.StyleDefault)
s.SetContent(b.w-1, b.y, tcell.RuneURCorner, nil, tcell.StyleDefault)
s.SetContent(b.x, b.h-1, tcell.RuneLLCorner, nil, tcell.StyleDefault)
s.SetContent(b.w-1, b.h-1, tcell.RuneLRCorner, nil, tcell.StyleDefault)
if b.title != nil {
b.title.Draw(s)
}
if b.menuItems != nil {
b.menuItems.Draw(s)
}
for c := range b.contents {
b.contents[c].container.Draw(s)
}
}
type list struct {
x, y int
h, w int
selected int
listItems []string
}
func NewList(listItems []string, initialSelected int) *list {
return &list{
listItems: listItems,
selected: initialSelected,
}
}
func (l *list) SetSize(x, y, h, w int) {
l.x, l.y, l.h, l.w = x, y, h, w
}
func (l *list) Draw(s tcell.Screen) {
for i := range l.listItems {
for j, r := range l.listItems[i] {
s.SetContent(l.x+j, l.y+i, r, nil, tcell.StyleDefault)
}
if i == l.selected {
s.SetContent(l.x+len(l.listItems[i])+1, l.y+i, '<', nil, tcell.StyleDefault)
}
}
}
type paddedText struct {
x, y int
h, w int
text string
}
func NewPaddedText(text string) *paddedText {
return &paddedText{text: text}
}
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) Draw(s tcell.Screen) {
t := p.x
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault)
t++
for _, r := range p.text {
s.SetContent(t, p.y, r, nil, tcell.StyleDefault)
t++
}
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault)
}

View File

@ -5,6 +5,7 @@ import (
"log"
"git.yetaga.in/alazyreader/library/config"
"git.yetaga.in/alazyreader/library/ui"
"github.com/gdamore/tcell"
"github.com/kelseyhightower/envconfig"
)
@ -24,21 +25,31 @@ func main() {
log.Fatalln(err)
}
l := NewList([]string{"foo", "bar", "baz"}, 0)
b := NewBox(
l := ui.NewList([]string{"foo", "bar", "baz"}, 0)
menu := ui.NewBox(
"library",
[]string{"(e)dit", "(q)uit"},
contents{{
offsets: offsets{top: 1, left: 2, bottom: -2, right: -2},
container: l,
ui.Contents{{
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2},
Container: l,
}},
)
activeBook := ui.NewBox(
"book",
[]string{"test"},
ui.Contents{},
)
container := ui.NewContainer(
ui.Contents{{Container: menu}, {Container: activeBook}},
ui.LayoutHorizontalEven,
)
// init
screen.Clear()
w, h := screen.Size()
b.SetSize(0, 0, h, w)
b.Draw(screen)
container.SetSize(0, 0, h, w)
container.Draw(screen)
screen.Sync()
// UI loop
@ -48,11 +59,11 @@ func main() {
case *tcell.EventError:
screen.Beep()
case *tcell.EventKey: // input handling
if v.Key() == tcell.KeyUp && l.selected > 0 {
l.selected = l.selected - 1
if v.Key() == tcell.KeyUp && l.Selected() > 0 {
l.SetSelected(l.Selected() - 1)
}
if v.Key() == tcell.KeyDown && l.selected < len(l.listItems)-1 {
l.selected = l.selected + 1
if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 {
l.SetSelected(l.Selected() + 1)
}
if v.Rune() == 'q' {
screen.Fini()
@ -60,17 +71,17 @@ func main() {
return
}
screen.Clear()
b.Draw(screen)
container.Draw(screen)
case *tcell.EventResize: // screen redraw
w, h := screen.Size()
b.SetSize(0, 0, h, w)
container.SetSize(0, 0, h, w)
screen.Clear()
b.Draw(screen)
container.Draw(screen)
case *tcell.EventInterrupt:
case *tcell.EventMouse:
case *tcell.EventTime:
default:
}
screen.Sync() // repaint
screen.Show() // repaint
}
}

226
ui/ui.go Normal file
View File

@ -0,0 +1,226 @@
package ui
import (
"strings"
"github.com/gdamore/tcell"
)
type Drawable interface {
Draw(tcell.Screen)
SetSize(x, y, h, w int)
}
type Offsets struct {
Top int
Bottom int
Left int
Right int
}
type Contents []struct {
Offsets Offsets
Container Drawable
}
const (
LayoutUnmanaged = iota
LayoutHorizontalEven
LayoutVerticalEven
)
// 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
// have it compute even distributions of space for all components either horizontally or vertically,
// filling the container.
type Container struct {
x, y int
h, w int
layoutMethod int
contents Contents
}
func NewContainer(contents Contents, layoutMethod int) *Container {
return &Container{
layoutMethod: layoutMethod,
contents: contents,
}
}
func (c *Container) Draw(s tcell.Screen) {
for i := range c.contents {
c.contents[i].Container.Draw(s)
}
}
func (c *Container) SetSize(x, y, h, w int) {
c.x, c.y, c.h, c.w = x, y, h, w
if c.layoutMethod == LayoutVerticalEven {
num := len(c.contents)
for r := range c.contents {
x := c.x
y := c.y + (num * (r + 1))
h := c.h / num
w := c.w
c.contents[r].Container.SetSize(x, y, h, w)
}
} else if c.layoutMethod == LayoutHorizontalEven {
num := len(c.contents)
for r := range c.contents {
w := c.w / num
h := c.h
x := c.x + (w * r)
y := c.y
c.contents[r].Container.SetSize(x, y, h, w)
}
} else {
for r := range c.contents {
x := c.x + c.contents[r].Offsets.Left
y := c.y + c.contents[r].Offsets.Top
h := c.h - c.contents[r].Offsets.Bottom
w := c.w - c.contents[r].Offsets.Right
c.contents[r].Container.SetSize(x, y, h, w)
}
}
}
func (c *Container) Contents() Contents {
return c.contents
}
func (c *Container) SetContents(con Contents) {
c.contents = con
}
// A Box draws a ASCII box around its contents, with an optional title and footer.
type Box struct {
x, y int
h, w int
title Drawable
menuItems Drawable
contents Contents
}
func NewBox(title string, menuItems []string, contents Contents) *Box {
return &Box{
title: NewPaddedText(title),
menuItems: NewPaddedText(strings.Join(menuItems, " ")),
contents: contents,
}
}
func (b *Box) SetSize(x, y, h, w int) {
b.x, b.y, b.h, b.w = x, y, h, w
b.title.SetSize(b.x+2, b.y, 0, 0)
b.menuItems.SetSize(b.x+2, b.y+b.h-1, 0, 0)
for c := range b.contents {
x := b.x + b.contents[c].Offsets.Left
y := b.y + b.contents[c].Offsets.Top
h := b.h - b.contents[c].Offsets.Bottom
w := b.w - b.contents[c].Offsets.Right
b.contents[c].Container.SetSize(x, y, h, w)
}
}
func (b *Box) Draw(s tcell.Screen) {
for m := 1; m < b.w-1; m++ {
s.SetContent(m, b.y, tcell.RuneHLine, nil, tcell.StyleDefault)
s.SetContent(m, b.h-1, tcell.RuneHLine, nil, tcell.StyleDefault)
}
for m := 1; m < b.h-1; m++ {
s.SetContent(b.x, m, tcell.RuneVLine, nil, tcell.StyleDefault)
s.SetContent(b.w-1, m, tcell.RuneVLine, nil, tcell.StyleDefault)
}
s.SetContent(b.x, b.y, tcell.RuneULCorner, nil, tcell.StyleDefault)
s.SetContent(b.w-1, b.y, tcell.RuneURCorner, nil, tcell.StyleDefault)
s.SetContent(b.x, b.h-1, tcell.RuneLLCorner, nil, tcell.StyleDefault)
s.SetContent(b.w-1, b.h-1, tcell.RuneLRCorner, nil, tcell.StyleDefault)
if b.title != nil {
b.title.Draw(s)
}
if b.menuItems != nil {
b.menuItems.Draw(s)
}
for c := range b.contents {
b.contents[c].Container.Draw(s)
}
}
func (b *Box) Contents() Contents {
return b.contents
}
func (b *Box) SetContents(c Contents) {
b.contents = c
}
// A List is a scrollable, pageable list with a selector token.
type List struct {
x, y int
h, w int
selected int
listItems []string
}
func NewList(listItems []string, initialSelected int) *List {
return &List{
listItems: listItems,
selected: initialSelected,
}
}
func (l *List) SetSize(x, y, h, w int) {
l.x, l.y, l.h, l.w = x, y, h, w
}
func (l *List) Draw(s tcell.Screen) {
for i := range l.listItems {
for j, r := range l.listItems[i] {
s.SetContent(l.x+j, l.y+i, r, nil, tcell.StyleDefault)
}
if i == l.selected {
s.SetContent(l.x+len(l.listItems[i])+1, l.y+i, '<', nil, tcell.StyleDefault)
}
}
}
func (l *List) Selected() int {
return l.selected
}
func (l *List) SetSelected(i int) {
l.selected = i
}
func (l *List) ListMembers() []string {
return l.listItems
}
// 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
}
func NewPaddedText(text string) *PaddedText {
return &PaddedText{text: text}
}
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) Draw(s tcell.Screen) {
t := p.x
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault)
t++
for _, r := range p.text {
s.SetContent(t, p.y, r, nil, tcell.StyleDefault)
t++
}
s.SetContent(t, p.y, ' ', nil, tcell.StyleDefault)
}