starting to get a UI system working

master
Jordan Orelli 4 years ago
parent 30e47e6f7f
commit 19a753cf99

@ -21,29 +21,20 @@ func newBuffer(width, height int) *buffer {
return b return b
} }
func (b *buffer) set(x, y int, t tile) bool { func (b *buffer) setTile(x, y int, t tile) {
n := y*b.width + x n := y*b.width + x
if n >= len(b.tiles) { if n >= len(b.tiles) {
return false return
} }
b.tiles[n] = t b.tiles[n] = t
return true
} }
func (b *buffer) get(x, y int) (tile, bool) { func (b *buffer) getTile(x, y int) tile {
n := y*b.width + x n := y*b.width + x
if n >= len(b.tiles) { if n >= len(b.tiles) {
return tile{}, false return tile{}
}
return b.tiles[n], true
}
func (b *buffer) writeString(s string, start math.Vec, style tcell.Style) {
for i, r := range []rune(s) {
if !b.set(start.X+i, start.Y, tile{r: r, style: style}) {
break
}
} }
return b.tiles[n]
} }
func (b *buffer) clear() { func (b *buffer) clear() {
@ -55,18 +46,18 @@ func (b *buffer) clear() {
func (b *buffer) blit(s tcell.Screen, offset math.Vec) { func (b *buffer) blit(s tcell.Screen, offset math.Vec) {
for x := 0; x < b.width; x++ { for x := 0; x < b.width; x++ {
for y := 0; y < b.height; y++ { for y := 0; y < b.height; y++ {
t, ok := b.get(x, y) t := b.getTile(x, y)
if ok {
s.SetContent(x+offset.X, y+offset.Y, t.r, nil, t.style) s.SetContent(x+offset.X, y+offset.Y, t.r, nil, t.style)
} }
} }
}
} }
func (b *buffer) fill(style tcell.Style) { func (b *buffer) fill(style tcell.Style) {
for x := 0; x < b.width; x++ { for x := 0; x < b.width; x++ {
for y := 0; y < b.height; y++ { for y := 0; y < b.height; y++ {
b.set(x, y, tile{r: ' ', style: style}) b.setTile(x, y, tile{r: ' ', style: style})
} }
} }
} }
func (b *buffer) bounds() math.Rect { return math.Rect{Width: b.width, Height: b.height} }

@ -0,0 +1,43 @@
package app
import (
"github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/math"
)
type canvas interface {
getTile(x, y int) tile
setTile(int, int, tile)
bounds() math.Rect
}
func writeString(img canvas, s string, start math.Vec, style tcell.Style) {
for i, r := range []rune(s) {
img.setTile(start.X+i, start.Y, tile{r: r, style: style})
}
}
func cut(img canvas, bounds math.Rect) canvas {
return &section{img: img, frame: bounds}
}
type section struct {
img canvas
frame math.Rect
}
func (s *section) getTile(x, y int) tile {
if x < 0 || x >= s.frame.Width || y < 0 || y >= s.frame.Height {
return tile{}
}
return s.img.getTile(x+s.frame.Origin.X, y+s.frame.Origin.Y)
}
func (s *section) setTile(x, y int, t tile) {
if x < 0 || x >= s.frame.Width || y < 0 || y >= s.frame.Height {
return
}
s.img.setTile(x+s.frame.Origin.X, y+s.frame.Origin.Y, t)
}
func (s *section) bounds() math.Rect { return s.frame }

@ -16,7 +16,7 @@ type chatView struct {
history []sim.ChatMessage history []sim.ChatMessage
} }
func (c *chatView) handleEvent(ui *UI, e tcell.Event) bool { func (c *chatView) handleEvent(e tcell.Event) change {
switch t := e.(type) { switch t := e.(type) {
case *tcell.EventKey: case *tcell.EventKey:
key := t.Key() key := t.Key()
@ -31,10 +31,11 @@ func (c *chatView) handleEvent(ui *UI, e tcell.Event) bool {
} }
if key == tcell.KeyEnter { if key == tcell.KeyEnter {
c.composing = ""
return changeFn(func(ui *UI) {
// ugh lol // ugh lol
go ui.client.Send(sim.SendChatMessage{Text: c.composing}) go ui.client.Send(sim.SendChatMessage{Text: c.composing})
c.composing = "" })
break
} }
if key == tcell.KeyRune { if key == tcell.KeyRune {
@ -45,22 +46,23 @@ func (c *chatView) handleEvent(ui *UI, e tcell.Event) bool {
default: default:
// ui.Debug("screen saw unhandled event of type %T", e) // ui.Debug("screen saw unhandled event of type %T", e)
} }
return false return nil
} }
func (c *chatView) draw(b *buffer) { func (c *chatView) draw(img canvas, st *state) {
chatHeight := b.height - 1 bounds := img.bounds()
chatHeight := bounds.Height - 1
for i := 0; i < math.Min(chatHeight, len(c.history)); i++ { for i := 0; i < math.Min(chatHeight, len(c.history)); i++ {
msg := c.history[len(c.history)-1-i] msg := c.history[len(c.history)-1-i]
s := fmt.Sprintf("%12s: %s", msg.From, msg.Text) s := fmt.Sprintf("%12s: %s", msg.From, msg.Text)
b.writeString(s, math.Vec{0, b.height - 2 - i}, tcell.StyleDefault) writeString(img, s, math.Vec{0, bounds.Height - 2 - i}, tcell.StyleDefault)
} }
b.writeString(c.composing, math.Vec{0, b.height - 1}, tcell.StyleDefault) writeString(img, c.composing, math.Vec{0, bounds.Height - 1}, tcell.StyleDefault)
if c.inFocus { if c.inFocus {
cursor := tile{r: tcell.RuneBlock, style: tcell.StyleDefault.Blink(true)} cursor := tile{r: tcell.RuneBlock, style: tcell.StyleDefault.Blink(true)}
b.set(len([]rune(c.composing)), b.height-1, cursor) img.setTile(len([]rune(c.composing)), bounds.Height-1, cursor)
} }
} }

@ -0,0 +1,34 @@
package app
import (
"github.com/gdamore/tcell/v2"
"github.com/jordanorelli/astro-domu/internal/math"
)
type containerView struct {
children []*node
focussed int
}
func (c *containerView) handleEvent(e tcell.Event) change {
switch e.(type) {
case *tcell.EventKey:
return c.children[c.focussed].handleEvent(e)
default:
// ui.Debug("screen saw unhandled event of type %T", e)
}
return nil
}
func (c *containerView) draw(img canvas, st *state) {
for _, n := range c.children {
n.draw(cut(img, n.frame), st)
}
}
type node struct {
view
frame math.Rect
}

@ -22,10 +22,10 @@ func (f *form) handleEvent(e tcell.Event) change {
return f.fields[0].handleEvent(e) return f.fields[0].handleEvent(e)
} }
func (f *form) draw(b *buffer, _ *state) { func (f *form) draw(img canvas, _ *state) {
for i, field := range f.fields { for i, field := range f.fields {
b.writeString(field.label, math.Vec{0, i * 2}, tcell.StyleDefault) writeString(img, field.label, math.Vec{0, i * 2}, tcell.StyleDefault)
b.writeString(field.prompt+field.entered, math.Vec{0, i*2 + 1}, tcell.StyleDefault) writeString(img, field.prompt+field.entered, math.Vec{0, i*2 + 1}, tcell.StyleDefault)
} }
} }

@ -33,35 +33,34 @@ func (v *gameView) handleEvent(e tcell.Event) change {
return nil return nil
} }
func (v *gameView) draw(b *buffer, st *state) { func (v *gameView) draw(img canvas, st *state) {
v.drawHeader(b, st) v.drawHeader(img, st)
// fill in background dots first // fill in background dots first
for x := 0; x < st.room.Width; x++ { for x := 0; x < st.room.Width; x++ {
for y := 0; y < st.room.Height; y++ { for y := 0; y < st.room.Height; y++ {
b.set(x+1, y+2, tile{r: '·', style: tcell.StyleDefault}) img.setTile(x+1, y+2, tile{r: '·', style: tcell.StyleDefault})
} }
} }
b.set(0, 1, tile{r: '┌'}) img.setTile(0, 1, tile{r: '┌'})
b.set(st.room.Width+1, 1, tile{r: '┐'}) img.setTile(st.room.Width+1, 1, tile{r: '┐'})
b.set(0, st.room.Height+2, tile{r: '└'}) img.setTile(0, st.room.Height+2, tile{r: '└'})
b.set(st.room.Width+1, st.room.Height+2, tile{r: '┘'}) img.setTile(st.room.Width+1, st.room.Height+2, tile{r: '┘'})
for x := 0; x < st.room.Width; x++ { for x := 0; x < st.room.Width; x++ {
b.set(x+1, 1, tile{r: '─'}) img.setTile(x+1, 1, tile{r: '─'})
b.set(x+1, st.room.Height+2, tile{r: '─'}) img.setTile(x+1, st.room.Height+2, tile{r: '─'})
} }
for y := 0; y < st.room.Height; y++ { for y := 0; y < st.room.Height; y++ {
b.set(0, y+2, tile{r: '│'}) img.setTile(0, y+2, tile{r: '│'})
b.set(st.room.Width+1, y+2, tile{r: '│'}) img.setTile(st.room.Width+1, y+2, tile{r: '│'})
} }
for _, e := range st.room.Entities { for _, e := range st.room.Entities {
pos := e.Position pos := e.Position
b.set(pos.X+1, pos.Y+2, tile{r: e.Glyph, style: tcell.StyleDefault}) img.setTile(pos.X+1, pos.Y+2, tile{r: e.Glyph, style: tcell.StyleDefault})
} }
} }
func (v *gameView) drawHeader(b *buffer, st *state) { func (v *gameView) drawHeader(img canvas, st *state) {
// the first row is the name of the room that we're currently in // the first row is the name of the room that we're currently in
var style tcell.Style var style tcell.Style
style = style.Background(tcell.NewRGBColor(64, 64, 128)) style = style.Background(tcell.NewRGBColor(64, 64, 128))
@ -69,11 +68,12 @@ func (v *gameView) drawHeader(b *buffer, st *state) {
runes := []rune(st.room.Name) runes := []rune(st.room.Name)
for i := 0; i < b.width; i++ { bounds := img.bounds()
for i := 0; i < bounds.Width; i++ {
if i < len(runes) { if i < len(runes) {
b.set(i, 0, tile{r: runes[i], style: style}) img.setTile(i, 0, tile{r: runes[i], style: style})
} else { } else {
b.set(i, 0, tile{r: ' ', style: style}) img.setTile(i, 0, tile{r: ' ', style: style})
} }
} }
} }
@ -86,5 +86,5 @@ type move struct {
} }
func (m move) exec(ui *UI) { func (m move) exec(ui *UI) {
ui.client.Send(sim.Move{m.x, m.y}) go ui.client.Send(sim.Move{m.x, m.y})
} }

@ -1,6 +1,7 @@
package app package app
import ( import (
"github.com/jordanorelli/astro-domu/internal/math"
"github.com/jordanorelli/astro-domu/internal/wire" "github.com/jordanorelli/astro-domu/internal/wire"
) )
@ -37,10 +38,19 @@ func (l login) exec(ui *UI) {
// e := room.Entities[p.Avatar] // e := room.Entities[p.Avatar]
ui.state.room = &room ui.state.room = &room
ui.root = &node{ ui.root = &containerView{
children: []*node{
{
frame: math.Rect{math.Vec{0, 0}, 20, 20},
view: &gameView{ view: &gameView{
Log: ui.Child("game-view"), Log: ui.Child("game-view"),
}, },
},
{
frame: math.Rect{math.Vec{0, 20}, 40, 20},
view: &chatView{},
},
},
} }
ui.Info("done logging in, we replaced the root view whaduheck") ui.Info("done logging in, we replaced the root view whaduheck")
} }

@ -32,12 +32,12 @@ func (m *menuList) handleEvent(e tcell.Event) change {
return nil return nil
} }
func (m *menuList) draw(b *buffer, _ *state) { func (m *menuList) draw(img canvas, _ *state) {
for i, choice := range m.choices { for i, choice := range m.choices {
if i == m.highlight { if i == m.highlight {
b.writeString("▷ "+choice.name, math.Vec{0, i}, tcell.StyleDefault) writeString(img, "▷ "+choice.name, math.Vec{0, i}, tcell.StyleDefault)
} else { } else {
b.writeString(" "+choice.name, math.Vec{0, i}, tcell.StyleDefault) writeString(img, " "+choice.name, math.Vec{0, i}, tcell.StyleDefault)
} }
} }
} }

@ -1,6 +0,0 @@
package app
type node struct {
view
children []*node
}

@ -16,9 +16,9 @@ type statusBar struct {
func (s *statusBar) handleEvent(ui *UI, e tcell.Event) bool { return false } func (s *statusBar) handleEvent(ui *UI, e tcell.Event) bool { return false }
func (s *statusBar) draw(b *buffer) { func (s *statusBar) draw(img canvas, st *state) {
line := fmt.Sprintf("shown: %08d cleared: %08d messages: %08d", s.showCount, s.clearCount, s.msgCount) line := fmt.Sprintf("shown: %08d cleared: %08d messages: %08d", s.showCount, s.clearCount, s.msgCount)
b.writeString(line, math.Vec{0, 0}, tcell.StyleDefault) writeString(img, line, math.Vec{0, 0}, tcell.StyleDefault)
} }
func (s *statusBar) setFocus(enabled bool) { s.inFocus = enabled } func (s *statusBar) setFocus(enabled bool) { s.inFocus = enabled }

@ -36,6 +36,6 @@ func (t *textInput) handleEvent(e tcell.Event) change {
return nil return nil
} }
func (t *textInput) draw(b *buffer, _ *state) { func (t *textInput) draw(img canvas, _ *state) {
b.writeString(t.prompt+t.entered, math.Vec{0, 0}, tcell.StyleDefault) writeString(img, t.prompt+t.entered, math.Vec{0, 0}, tcell.StyleDefault)
} }

@ -18,7 +18,7 @@ type UI struct {
notifications <-chan wire.Response notifications <-chan wire.Response
state state state state
root *node root view
} }
func (ui *UI) Run() { func (ui *UI) Run() {
@ -164,8 +164,7 @@ func (ui *UI) pollInput(c chan tcell.Event) {
} }
} }
var mainMenu = &node{ var mainMenu = &menuList{
view: &menuList{
choices: []menuItem{ choices: []menuItem{
menuItem{ menuItem{
name: "join", name: "join",
@ -180,11 +179,9 @@ var mainMenu = &node{
}), }),
}, },
}, },
},
} }
var joinForm = &node{ var joinForm = &form{
view: &form{
fields: []textField{ fields: []textField{
textField{ textField{
label: "What is your name?", label: "What is your name?",
@ -193,5 +190,4 @@ var joinForm = &node{
}, },
}, },
}, },
},
} }

@ -6,7 +6,7 @@ import (
type view interface { type view interface {
handleEvent(tcell.Event) change handleEvent(tcell.Event) change
draw(*buffer, *state) draw(canvas, *state)
} }
type focusable interface { type focusable interface {

Loading…
Cancel
Save