ListWidget is Rendering Again

The ListWidget rendering has been updated to support the new
architecture.
1. Borders/Styles
2. golang Image package
3. Blocks
This commit is contained in:
Kevin Cotugno 2017-12-30 02:23:20 -08:00
parent 82770ab0a0
commit a73e0f6a27
8 changed files with 232 additions and 256 deletions

View File

@ -5,12 +5,23 @@ import (
)
type Block struct {
Rect image.Rectangle
Rect image.Rectangle
Cells map[image.Point]Cell
}
func NewBlock() Block {
func NewBlock(originx, originy, sizex, sizey int) Block {
b := Block{}
b.Cells := make(map[image.Point]Cell)
b.Rect = image.Rect(originx, originy, originx+sizex, originy+sizey)
b.Cells = make(map[image.Point]Cell)
return b
}
func (b *Block) SetSize(p image.Point) {
dx := b.Rect.Min.X
dy := b.Rect.Min.Y
b.Rect.Max.X = p.X + dx
b.Rect.Max.Y = p.Y + dy
}
func (b *Block) SetOrigin(p image.Point) {
}

53
exhibit/border.go Normal file
View File

@ -0,0 +1,53 @@
package exhibit
const (
Thick = Style(iota)
Thin
ThickBroken
ThinBroken
Double
)
const (
Vertical = Box(iota)
Horizontal
TopRight
TopLeft
BottomRight
BottomLeft
VerticalRight
VerticalLeft
HorizontalUp
HorizontalDown
Intersect
)
type Style int
type Box int
var thick = []rune{'┃', '━', '┓', '┏', '┛', '┗', '┣', '┫', '┻', '┳', '╋'}
var thin = []rune{'│', '─', '┐', '┌', '┘', '└', '├', '┤', '┴', '┬', '┼'}
var thickBroken = []rune{'┇', '┅', '┓', '┏', '┛', '┗', '┣', '┫', '┻', '┳', '╋'}
var thinBroken = []rune{'┆', '┄', '┐', '┌', '┘', '└', '├', '┤', '┴', '┬', '┼'}
var double = []rune{'║', '═', '╗', '╔', '╝', '╚', '╠', '╣', '╩', '╦', '╬'}
func BorderRune(c Box, s Style) rune {
switch s {
case Thick:
return thick[c]
case Thin:
return thin[c]
case ThickBroken:
return thickBroken[c]
case ThinBroken:
return thinBroken[c]
case Double:
return double[c]
default:
return ' '
}
}

View File

@ -1,6 +1,7 @@
package exhibit
import (
"image"
"sync"
)
@ -10,12 +11,11 @@ type ListEntry interface {
}
type ListWidget struct {
constraints Constraints
attributes Attributes
size Size
Style Style
cellLock sync.Mutex
cells [][]Cell
blockLock sync.Mutex
block Block
attributes Attributes
rightAlign bool
border bool
@ -24,8 +24,6 @@ type ListWidget struct {
list [][]Cell
listBuf [][]Cell
lastSize Size
}
func (l ListWidget) Attributes() Attributes {
@ -37,72 +35,32 @@ func (l *ListWidget) SetAttributes(a Attributes) {
l.recalculateCells()
}
func (l ListWidget) Constraints() Constraints {
return l.constraints
func (l ListWidget) Size() image.Point {
return l.block.Rect.Size()
}
func (l *ListWidget) SetConstraints(c Constraints) {
l.constraints = c
func (l *ListWidget) SetSize(p image.Point) {
l.block.SetSize(p)
}
func (l ListWidget) Size() Size {
return l.size
}
func (l *ListWidget) Render() Block {
l.blockLock.Lock()
defer l.blockLock.Unlock()
func (l *ListWidget) SetSize(s Size) {
l.size = s
}
b := NewBlock(0, 0, 0, 0)
b.Rect = l.block.Rect
func (l *ListWidget) Render() [][]Cell {
l.cellLock.Lock()
defer l.cellLock.Unlock()
var sx int
sy := len(l.cells)
if sy > 0 {
sx = len(l.cells[0])
} else {
return make([][]Cell, 0)
for k, v := range l.block.Cells {
b.Cells[k] = v
}
dx := 0
dy := 0
if l.lastSize.X > sx {
dx = l.lastSize.X - sx
}
if l.lastSize.Y > sy {
dy = l.lastSize.Y - sy
}
for y := 0; y < sy+dy; y++ {
if y >= sy {
l.cells = append(l.cells, []Cell{})
}
for x := 0; x < sx+dx; x++ {
if x >= sx || y >= sy {
c := Cell{}
c.Pos.X = x
c.Pos.Y = y
c.Value = ' '
l.cells[y] = append(l.cells[y], c)
} else {
l.cells[y][x].Pos.X = x
l.cells[y][x].Pos.Y = y
}
}
}
l.size.X = sx
l.size.Y = sy
l.lastSize = l.size
return append([][]Cell(nil), l.cells...)
return b
}
func (l *ListWidget) AddEntry(entry ListEntry) {
l.listLock.Lock()
defer l.listLock.Unlock()
if l.listBuf == nil {
l.listBuf = make([][]Cell, 1)
} else {
@ -152,65 +110,75 @@ func (l *ListWidget) recalculateCells() {
l.listLock.Lock()
defer l.listLock.Unlock()
var longest int
var border int
l.blockLock.Lock()
size := l.block.Rect.Size()
l.blockLock.Unlock()
for _, s := range l.list {
if longest < len(s) {
longest = len(s)
}
}
cells := make([][]Cell, len(l.list))
cells := make(map[image.Point]Cell)
var i, bx, by int
if l.border {
border = 1
top := make([]Cell, longest+2)
top[0] = Cell{Value: '┏', Attrs: l.Attributes()}
top[longest+1] = Cell{Value: '┓', Attrs: l.Attributes()}
for i := 1; i <= longest; i++ {
top[i] = Cell{Value: '━', Attrs: l.Attributes()}
}
bottom := append([]Cell(nil), top...)
bottom[0] = Cell{Value: '┗', Attrs: l.Attributes()}
bottom[longest+1] = Cell{Value: '┛', Attrs: l.Attributes()}
cells = append([][]Cell{top}, cells...)
cells = append(cells, bottom)
size = size.Add(image.Point{2, 2})
bx = 1
by = 1
}
for i, s := range l.list {
cells[i+border] = make([]Cell, longest+border+border)
for x := 0; x < size.X; x++ {
for y := 0; y < size.Y; y++ {
c := Cell{Value: ' '}
var start int
if l.rightAlign {
start = longest - len(s)
} else {
start = 0
}
if l.border {
c := Cell{Value: '┃', Attrs: l.Attributes()}
cells[i+border][0] = c
cells[i+border][longest+1] = c
}
for j := 0; j < longest; j++ {
c := Cell{}
if j > start+len(s)-1 || j < start {
c.Value = ' '
} else {
c = s[j-start]
if l.border {
cell, ok := l.borderCell(image.Point{x, y}, size)
if ok {
cells[image.Point{x, y}] = cell
continue
}
}
cells[i+border][j+border] = c
if y < len(l.list)+by {
length := len(l.list[y-by])
if l.rightAlign {
i = (size.X - x - length - bx) * -1
} else {
i = x - bx
}
if i < length && i >= 0 {
c = l.list[y-by][i]
}
cells[image.Point{x, y}] = c
}
}
}
l.cellLock.Lock()
defer l.cellLock.Unlock()
l.cells = cells
l.blockLock.Lock()
l.block.Cells = cells
l.blockLock.Unlock()
}
func (l *ListWidget) borderCell(p image.Point, size image.Point) (Cell, bool) {
c := Cell{}
c.Attrs = l.Attributes()
if p.X != 0 && p.X != size.X-1 && p.Y != 0 && p.Y != size.Y-1 {
return c, false
}
if p.X == 0 && p.Y == 0 {
c.Value = BorderRune(TopLeft, l.Style)
} else if p.X == size.X-1 && p.Y == 0 {
c.Value = BorderRune(TopRight, l.Style)
} else if p.X == 0 && p.Y == size.Y-1 {
c.Value = BorderRune(BottomLeft, l.Style)
} else if p.X == size.X-1 && p.Y == size.Y-1 {
c.Value = BorderRune(BottomRight, l.Style)
} else if p.X == 0 || p.X == size.X-1 {
c.Value = BorderRune(Vertical, l.Style)
} else if p.Y == 0 || p.Y == size.Y-1 {
c.Value = BorderRune(Horizontal, l.Style)
}
return c, true
}

View File

@ -6,14 +6,13 @@ type Scene struct {
}
func (s *Scene) Render() {
// s.Window.SetSize(s.Terminal.Size())
// s.Window.SetSize(s.Terminal.Size())
c := make([]Cell, 0)
for _, row := range s.Window.Render() {
for _, col := range row {
c = append(c, col)
}
for k, v := range s.Window.Render().Cells {
v.Point = k
c = append(c, v)
}
s.Terminal.WriteCells(c)

View File

@ -5,12 +5,12 @@ import (
"bytes"
"fmt"
"image"
"io"
"log"
"os"
"sync"
"time"
"unicode/utf8"
)
const (
@ -34,17 +34,13 @@ type Terminal struct {
bufLock sync.Mutex
buffer *bytes.Buffer
sizeLock sync.Mutex
size Size
displayLock sync.Mutex
display [][]Cell
display Block
interLock sync.Mutex
interBuf []Cell
currentAttributes Attributes
cursorPosition Position
cursorVisible bool
termios unix.Termios
@ -124,8 +120,6 @@ func (t *Terminal) Clear() {
func (t *Terminal) SetCursor(x, y int) {
t.writeBuffer([]byte(fmt.Sprintf(cup, y+1, x+1)))
t.cursorPosition.X = x
t.cursorPosition.Y = y
}
func (t *Terminal) ShowCursor() {
@ -142,65 +136,21 @@ func (t *Terminal) CursorVisible() bool {
return t.cursorVisible
}
func (t *Terminal) Size() Size {
t.sizeLock.Lock()
defer t.sizeLock.Unlock()
return t.size
func (t *Terminal) Size() image.Point {
return t.display.Rect.Size()
}
func (t *Terminal) setSize(x, y int) {
t.sizeLock.Lock()
defer t.sizeLock.Unlock()
if t.size.X == x && t.size.Y == y {
return
}
t.displayLock.Lock()
defer t.displayLock.Unlock()
t.size.X = x
t.size.Y = y
if t.display == nil {
t.display = make([][]Cell, x)
} else if len(t.display) < x {
t.display = append(t.display, make([][]Cell, x-len(t.display))...)
}
for i := 0; i < x; i++ {
if t.display[i] == nil {
t.display[i] = make([]Cell, y)
} else if len(t.display[i]) < y {
t.display[i] = append(t.display[i],
make([]Cell, y-len(t.display[i]))...)
}
}
t.Clear()
}
func (t *Terminal) WriteString(s string, x, y int, attrs Attributes) {
if len(s) == 0 || len(t.display) < x+1 || len(t.display[0]) < y+1 {
if t.display.Rect.Size().X == x && t.display.Rect.Size().Y == y {
return
}
var j int
for i := 0; i < len(s); i++ {
r, sz := utf8.DecodeRuneInString(s[j:])
t.display = NewBlock(0, 0, x, y)
if sz > 0 {
cell := Cell{Position{x + i, y}, r, attrs}
if t.display[x+i][y] != cell {
t.interBuf = append(t.interBuf, cell)
}
j = j + sz
}
}
t.Clear()
}
func (t *Terminal) WriteCells(cells []Cell) {
@ -260,24 +210,27 @@ func (t *Terminal) reconcileCells() {
defer t.interLock.Unlock()
var changed bool
sz := t.Size()
sz := t.display.Rect.Size()
for _, c := range t.interBuf {
if c.Pos.X >= sz.X || c.Pos.Y >= sz.Y {
if c.Point.X >= sz.X || c.Point.Y >= sz.Y {
continue
}
t.display[c.Pos.X][c.Pos.Y] = c
current := t.display.Cells[c.Point]
if current.Value == c.Value {
continue
}
t.display.Cells[c.Point] = c
if t.currentAttributes != c.Attrs {
changed = true
t.writeAttrs(c.Attrs)
}
if t.cursorPosition.X+1 != c.Pos.X {
t.SetCursor(c.Pos.X, c.Pos.Y)
}
t.SetCursor(c.Point.X, c.Point.Y)
t.writeRune(c.Value)
}
@ -291,20 +244,7 @@ func (t *Terminal) reconcileCells() {
}
func (t *Terminal) writeRune(r rune) {
t.writeBuffer([]byte(string(r)))
if t.cursorPosition.X+1 <= t.size.X {
t.cursorPosition.X = 1
} else {
t.cursorPosition.X++
}
if t.cursorPosition.Y+1 <= t.size.Y {
t.cursorPosition.Y = 1
} else {
t.cursorPosition.Y++
}
}
func (t *Terminal) writeAttrs(attrs Attributes) {

View File

@ -1,5 +1,9 @@
package exhibit
import (
"image"
)
type Border struct {
Top bool
Bottom bool
@ -8,11 +12,9 @@ type Border struct {
}
type Widget interface {
Render() [][]Cell
Constraints() Constraints
SetConstraints(Constraints)
Size() Size
SetSize(size Size)
Render() Block
Size() image.Point
SetSize(image.Point)
Attributes() Attributes
SetAttributes(attrs Attributes)
SetAttributes(Attributes)
}

View File

@ -5,10 +5,10 @@ import (
)
type WindowWidget struct {
block block
attributes Attributes
border Border
widgets []Widget
block Block
attributes Attributes
border Border
widgets []Widget
}
func (w *WindowWidget) AddWidget(widget Widget) {
@ -27,61 +27,54 @@ func (w *WindowWidget) SetAttributes(a Attributes) {
w.attributes = a
}
func (w WindowWidget) Constraints() Constraints {
return w.constraints
func (w WindowWidget) Size() image.Point {
return w.block.Rect.Size()
}
func (w *WindowWidget) SetConstraints(c Constraints) {
w.constraints = c
func (w *WindowWidget) SetSize(p image.Point) {
w.block.SetSize(p)
}
func (w WindowWidget) Size() Size {
return w.size
}
func (w *WindowWidget) SetSize(s Size) {
w.size = s
}
func (w *WindowWidget) Render() [][]Cell {
if w.size.X == 0 || w.size.Y == 0 {
func (w *WindowWidget) Render() Block {
if w.block.Rect.Size().X == 0 || w.block.Rect.Size().Y == 0 {
return w.renderContent()
} else {
return w.renderSize()
}
}
func (w *WindowWidget) renderContent() [][]Cell {
c := make([][]Cell, 0)
func (w *WindowWidget) renderContent() Block {
// c := make([][]Cell, 0)
var y int
// var y int
for _, w := range w.widgets {
for _, row := range w.Render() {
// for _, w := range w.widgets {
// for _, row := range w.Render() {
t := make([]Cell, len(row))
c = append(c, t)
// t := make([]Cell, len(row))
// c = append(c, t)
for j, col := range row {
col.Pos.Y = y
c[y][j] = col
}
// for j, col := range row {
// col.Pos.Y = y
// c[y][j] = col
// }
y++
}
}
// y++
// }
// }
return c
return Block{}
}
func (w *WindowWidget) renderSize() [][]Cell {
c := make([][]Cell, w.size.Y)
func (w *WindowWidget) renderSize() Block {
// c := make([][]Cell, w.size.Y)
for y := 0; y < w.size.Y; y++ {
for x := 0; x < w.size.X; x++ {
c[y][x] = Cell{Pos: Position{X: x, Y: y}}
}
}
// for y := 0; y < w.size.Y; y++ {
// for x := 0; x < w.size.X; x++ {
// c[y][x] = Cell{Pos: Position{X: x, Y: y}}
// }
// }
return c
// return c
return Block{}
}

View File

@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"image"
"io/ioutil"
"log"
"net/http"
@ -76,25 +77,27 @@ func main() {
defer terminal.Shutdown()
terminal.HideCursor()
window = &exhibit.WindowWidget{}
// window = &exhibit.WindowWidget{}
topAsks = &exhibit.ListWidget{}
// topAsks.SetSize(image.Point{100, 100})
topAsks.SetBorder(true)
topAsks.SetRightAlign(true)
topAsks.SetAttributes(exhibit.Attributes{ForegroundColor: exhibit.FGYellow})
topAsks.SetAttributes(exhibit.Attributes{ForegroundColor: exhibit.FGCyan})
topBids = &exhibit.ListWidget{}
topBids.SetBorder(true)
topBids.SetRightAlign(true)
topBids.SetAttributes(exhibit.Attributes{ForegroundColor: exhibit.FGGreen})
// topBids.SetBorder(true)
// topBids.SetRightAlign(true)
// topBids.SetAttributes(exhibit.Attributes{ForegroundColor: exhibit.FGGreen})
midPrice = &exhibit.ListWidget{}
midPrice.SetRightAlign(true)
// midPrice.SetRightAlign(true)
window.AddWidget(topAsks)
// window.AddWidget(topAsks)
// window.AddWidget(midPrice)
// window.AddWidget(topBids)
scene := exhibit.Scene{terminal, window}
// scene := exhibit.Scene{terminal, window}
scene := exhibit.Scene{terminal, topAsks}
conn, _, err := websocket.DefaultDialer.Dial("wss://ws-feed.gdax.com", nil)
if err != nil {
@ -103,12 +106,16 @@ func main() {
defer conn.Close()
go func() {
Loop:
for e := range terminal.Event {
if e == exhibit.EventCtrC || e == exhibit.Eventq {
switch e {
case exhibit.Eventq:
fallthrough
case exhibit.EventCtrC:
conn.WriteMessage(websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.
CloseNormalClosure, ""))
break
break Loop
}
}
}()
@ -165,6 +172,10 @@ func main() {
sz := terminal.Size()
num := numOfOrderPerSide(sz.Y)
aP := image.Point{14, num}
if topAsks.Size() != aP {
topAsks.SetSize(aP)
}
aIt := asks.Iterator()
@ -177,8 +188,7 @@ func main() {
price, size := flatten(entries)
asks[i] = ListEntry{Value: size.StringFixed(8),
Attrs: exhibit.Attributes{ForegroundColor:
exhibit.FGBlue}}
Attrs: exhibit.Attributes{ForegroundColor: exhibit.FGMagenta}}
if i == 0 {
low = price