diff --git a/ui/context.go b/ui/context.go new file mode 100644 index 0000000..9f2e2fe --- /dev/null +++ b/ui/context.go @@ -0,0 +1,95 @@ +package ui + +import ( + "fmt" + + "github.com/nsf/termbox-go" +) + +// A context allows you to draw in a sub-region of the terminal +type Context struct { + x int + y int + width int + height int +} + +func (ctx *Context) Width() int { + return ctx.width +} + +func (ctx *Context) Height() int { + return ctx.height +} + +func NewContext() *Context { + width, height := termbox.Size() + return &Context{0, 0, width, height} +} + +func (ctx *Context) Subcontext(x, y, width, height int) *Context { + if x+width > ctx.width || y+height > ctx.height { + panic(fmt.Errorf("Attempted to create context larger than parent")) + } + return &Context{ + x: ctx.x + x, + y: ctx.y + y, + width: width, + height: height, + } +} + +func (ctx *Context) SetCell(x, y int, ch rune, fg, bg termbox.Attribute) { + if x >= ctx.width || y >= ctx.height { + panic(fmt.Errorf("Attempted to draw outside of context")) + } + termbox.SetCell(ctx.x+x, ctx.y+y, ch, fg, bg) +} + +func (ctx *Context) Printf(x, y int, ref termbox.Cell, + format string, a ...interface{}) { + + if x >= ctx.width || y >= ctx.height { + panic(fmt.Errorf("Attempted to draw outside of context")) + } + + str := fmt.Sprintf(format, a...) + + x += ctx.x + y += ctx.y + old_x := x + + newline := func() bool { + x = old_x + y++ + return y < ctx.height + } + for _, ch := range str { + switch ch { + case '\n': + if !newline() { + return + } + case '\r': + x = old_x + default: + termbox.SetCell(x, y, ch, ref.Fg, ref.Bg) + x++ + if x == old_x+ctx.width { + if !newline() { + return + } + } + } + } +} + +func (ctx *Context) Fill(x, y, width, height int, ref termbox.Cell) { + _x := x + for ; y < height && y < ctx.height; y++ { + for ; x < width && x < ctx.width; x++ { + ctx.SetCell(x, y, ref.Ch, ref.Fg, ref.Bg) + } + x = _x + } +} diff --git a/ui/drawable.go b/ui/drawable.go new file mode 100644 index 0000000..eb60463 --- /dev/null +++ b/ui/drawable.go @@ -0,0 +1,8 @@ +package ui + +type Drawable interface { + // Called when this renderable should draw itself + Draw(ctx Context) + // Specifies a function to call when this cell needs to be redrawn + OnInvalidate(callback func(d Drawable)) +} diff --git a/ui/grid.go b/ui/grid.go new file mode 100644 index 0000000..fd0cab7 --- /dev/null +++ b/ui/grid.go @@ -0,0 +1,74 @@ +package ui + +import "fmt" + +type Grid struct { + Rows []DimSpec + Columns []DimSpec + Cells []*GridCell + onInvalidate func(d Drawable) +} + +const ( + SIZE_EXACT = iota + SIZE_WEIGHT = iota +) + +// Specifies the layout of a single row or column +type DimSpec struct { + // One of SIZE_EXACT or SIZE_WEIGHT + Strategy uint + // If Strategy = SIZE_EXACT, this is the number of cells this dim shall + // occupy. If SIZE_WEIGHT, the space left after all exact dims are measured + // is distributed amonst the remaining dims weighted by this value. + Size *uint +} + +type GridCell struct { + Row uint + Column uint + RowSpan uint + ColSpan uint + Content Drawable + invalid bool +} + +func (grid *Grid) Draw(ctx Context) { + // TODO +} + +func (grid *Grid) OnInvalidate(onInvalidate func(d Drawable)) { + grid.onInvalidate = onInvalidate +} + +func (grid *Grid) AddChild(cell *GridCell) { + grid.Cells = append(grid.Cells, cell) + cell.Content.OnInvalidate(grid.cellInvalidated) + cell.invalid = true +} + +func (grid *Grid) RemoveChild(cell *GridCell) { + for i, _cell := range grid.Cells { + if _cell == cell { + grid.Cells = append(grid.Cells[:i], grid.Cells[i+1:]...) + break + } + } +} + +func (grid *Grid) cellInvalidated(drawable Drawable) { + var cell *GridCell + for _, cell = range grid.Cells { + if cell.Content == drawable { + break + } + cell = nil + } + if cell == nil { + panic(fmt.Errorf("Attempted to invalidate unknown cell")) + } + cell.invalid = true + if grid.onInvalidate != nil { + grid.onInvalidate(grid) + } +}