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
}
func (b *buffer) set(x, y int, t tile) bool {
func (b *buffer) setTile(x, y int, t tile) {
n := y*b.width + x
if n >= len(b.tiles) {
return false
return
}
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
if n >= len(b.tiles) {
return tile{}, false
}
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 tile{}
}
return b.tiles[n]
}
func (b *buffer) clear() {
@ -55,10 +46,8 @@ func (b *buffer) clear() {
func (b *buffer) blit(s tcell.Screen, offset math.Vec) {
for x := 0; x < b.width; x++ {
for y := 0; y < b.height; y++ {
t, ok := b.get(x, y)
if ok {
s.SetContent(x+offset.X, y+offset.Y, t.r, nil, t.style)
}
t := b.getTile(x, y)
s.SetContent(x+offset.X, y+offset.Y, t.r, nil, t.style)
}
}
}
@ -66,7 +55,9 @@ func (b *buffer) blit(s tcell.Screen, offset math.Vec) {
func (b *buffer) fill(style tcell.Style) {
for x := 0; x < b.width; x++ {
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
}
func (c *chatView) handleEvent(ui *UI, e tcell.Event) bool {
func (c *chatView) handleEvent(e tcell.Event) change {
switch t := e.(type) {
case *tcell.EventKey:
key := t.Key()
@ -31,10 +31,11 @@ func (c *chatView) handleEvent(ui *UI, e tcell.Event) bool {
}
if key == tcell.KeyEnter {
// ugh lol
go ui.client.Send(sim.SendChatMessage{Text: c.composing})
c.composing = ""
break
return changeFn(func(ui *UI) {
// ugh lol
go ui.client.Send(sim.SendChatMessage{Text: c.composing})
})
}
if key == tcell.KeyRune {
@ -45,22 +46,23 @@ func (c *chatView) handleEvent(ui *UI, e tcell.Event) bool {
default:
// ui.Debug("screen saw unhandled event of type %T", e)
}
return false
return nil
}
func (c *chatView) draw(b *buffer) {
chatHeight := b.height - 1
func (c *chatView) draw(img canvas, st *state) {
bounds := img.bounds()
chatHeight := bounds.Height - 1
for i := 0; i < math.Min(chatHeight, len(c.history)); i++ {
msg := c.history[len(c.history)-1-i]
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 {
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)
}
func (f *form) draw(b *buffer, _ *state) {
func (f *form) draw(img canvas, _ *state) {
for i, field := range f.fields {
b.writeString(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.label, math.Vec{0, i * 2}, 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
}
func (v *gameView) draw(b *buffer, st *state) {
v.drawHeader(b, st)
func (v *gameView) draw(img canvas, st *state) {
v.drawHeader(img, st)
// fill in background dots first
for x := 0; x < st.room.Width; x++ {
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: '┌'})
b.set(st.room.Width+1, 1, tile{r: '┐'})
b.set(0, st.room.Height+2, tile{r: '└'})
b.set(st.room.Width+1, st.room.Height+2, tile{r: '┘'})
img.setTile(0, 1, tile{r: '┌'})
img.setTile(st.room.Width+1, 1, tile{r: '┐'})
img.setTile(0, 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++ {
b.set(x+1, 1, tile{r: '─'})
b.set(x+1, st.room.Height+2, tile{r: '─'})
img.setTile(x+1, 1, tile{r: '─'})
img.setTile(x+1, st.room.Height+2, tile{r: '─'})
}
for y := 0; y < st.room.Height; y++ {
b.set(0, y+2, tile{r: '│'})
b.set(st.room.Width+1, y+2, tile{r: '│'})
img.setTile(0, y+2, tile{r: '│'})
img.setTile(st.room.Width+1, y+2, tile{r: '│'})
}
for _, e := range st.room.Entities {
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
var style tcell.Style
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)
for i := 0; i < b.width; i++ {
bounds := img.bounds()
for i := 0; i < bounds.Width; i++ {
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 {
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) {
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
import (
"github.com/jordanorelli/astro-domu/internal/math"
"github.com/jordanorelli/astro-domu/internal/wire"
)
@ -37,9 +38,18 @@ func (l login) exec(ui *UI) {
// e := room.Entities[p.Avatar]
ui.state.room = &room
ui.root = &node{
view: &gameView{
Log: ui.Child("game-view"),
ui.root = &containerView{
children: []*node{
{
frame: math.Rect{math.Vec{0, 0}, 20, 20},
view: &gameView{
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")

@ -32,12 +32,12 @@ func (m *menuList) handleEvent(e tcell.Event) change {
return nil
}
func (m *menuList) draw(b *buffer, _ *state) {
func (m *menuList) draw(img canvas, _ *state) {
for i, choice := range m.choices {
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 {
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) 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)
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 }

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

@ -18,7 +18,7 @@ type UI struct {
notifications <-chan wire.Response
state state
root *node
root view
}
func (ui *UI) Run() {
@ -164,33 +164,29 @@ func (ui *UI) pollInput(c chan tcell.Event) {
}
}
var mainMenu = &node{
view: &menuList{
choices: []menuItem{
menuItem{
name: "join",
onSelect: changeFn(func(ui *UI) {
ui.root = joinForm
}),
},
menuItem{
name: "exit",
onSelect: changeFn(func(ui *UI) {
panic("this is bad programming")
}),
},
var mainMenu = &menuList{
choices: []menuItem{
menuItem{
name: "join",
onSelect: changeFn(func(ui *UI) {
ui.root = joinForm
}),
},
menuItem{
name: "exit",
onSelect: changeFn(func(ui *UI) {
panic("this is bad programming")
}),
},
},
}
var joinForm = &node{
view: &form{
fields: []textField{
textField{
label: "What is your name?",
textInput: textInput{
prompt: "> ",
},
var joinForm = &form{
fields: []textField{
textField{
label: "What is your name?",
textInput: textInput{
prompt: "> ",
},
},
},

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

Loading…
Cancel
Save