2018-03-28 02:01:35 +08:00
|
|
|
// Package canvas defines the canvas that the widgets draw on.
|
|
|
|
package canvas
|
|
|
|
|
|
|
|
import (
|
2018-03-29 02:34:20 +08:00
|
|
|
"fmt"
|
2018-03-28 02:01:35 +08:00
|
|
|
"image"
|
|
|
|
|
2018-03-29 08:28:36 +08:00
|
|
|
"github.com/mum4k/termdash/area"
|
2018-03-28 02:01:35 +08:00
|
|
|
"github.com/mum4k/termdash/cell"
|
2018-03-29 02:34:20 +08:00
|
|
|
"github.com/mum4k/termdash/terminalapi"
|
2018-03-28 02:01:35 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// Canvas is where a widget draws its output for display on the terminal.
|
2018-03-29 02:34:20 +08:00
|
|
|
type Canvas struct {
|
|
|
|
// area is the area the buffer was created for.
|
2018-03-29 08:28:36 +08:00
|
|
|
// Contains absolute coordinates on the target terminal, while the buffer
|
|
|
|
// contains relative zero-based coordinates for this canvas.
|
2018-03-29 02:34:20 +08:00
|
|
|
area image.Rectangle
|
2018-03-28 02:01:35 +08:00
|
|
|
|
2018-03-29 02:34:20 +08:00
|
|
|
// buffer is where the drawing happens.
|
|
|
|
buffer cell.Buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new Canvas with a buffer for the provided area.
|
2018-03-29 08:28:36 +08:00
|
|
|
func New(ar image.Rectangle) (*Canvas, error) {
|
|
|
|
if ar.Min.X < 0 || ar.Min.Y < 0 || ar.Max.X < 0 || ar.Max.Y < 0 {
|
|
|
|
return nil, fmt.Errorf("area cannot start or end on the negative axis, got: %+v", ar)
|
2018-03-29 02:34:20 +08:00
|
|
|
}
|
2018-03-29 08:28:36 +08:00
|
|
|
|
|
|
|
b, err := cell.NewBuffer(area.Size(ar))
|
2018-03-29 02:34:20 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Canvas{
|
2018-03-29 08:28:36 +08:00
|
|
|
area: ar,
|
2018-03-29 02:34:20 +08:00
|
|
|
buffer: b,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size returns the size of the 2-D canvas.
|
2018-03-28 02:01:35 +08:00
|
|
|
func (c *Canvas) Size() image.Point {
|
2018-03-29 02:34:20 +08:00
|
|
|
return c.buffer.Size()
|
2018-03-28 02:01:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear clears all the content on the canvas.
|
2018-03-29 02:34:20 +08:00
|
|
|
func (c *Canvas) Clear() error {
|
|
|
|
b, err := cell.NewBuffer(c.Size())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.buffer = b
|
|
|
|
return nil
|
|
|
|
}
|
2018-03-28 02:01:35 +08:00
|
|
|
|
|
|
|
// SetCell sets the value of the specified cell on the canvas.
|
|
|
|
// Use the options to specify which attributes to modify, if an attribute
|
|
|
|
// option isn't specified, the attribute retains its previous value.
|
2018-03-29 02:34:20 +08:00
|
|
|
func (c *Canvas) SetCell(p image.Point, r rune, opts ...cell.Option) error {
|
2018-03-29 08:28:36 +08:00
|
|
|
ar, err := area.FromSize(c.buffer.Size())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !p.In(ar) {
|
|
|
|
return fmt.Errorf("cell at point %+v falls out of the canvas area %+v", p, ar)
|
2018-03-29 02:34:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
cell := c.buffer[p.X][p.Y]
|
|
|
|
cell.Rune = r
|
|
|
|
cell.Apply(opts...)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-29 08:28:36 +08:00
|
|
|
// Apply applies the canvas to the corresponding area of the terminal.
|
2018-03-29 02:34:20 +08:00
|
|
|
// Guarantees to stay within limits of the area the canvas was created with.
|
|
|
|
func (c *Canvas) Apply(t terminalapi.Terminal) error {
|
2018-03-29 08:28:36 +08:00
|
|
|
termArea, err := area.FromSize(t.Size())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
bufArea, err := area.FromSize(c.buffer.Size())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bufArea.In(termArea) {
|
|
|
|
return fmt.Errorf("the canvas area %+v doesn't fit onto the terminal %+v", bufArea, termArea)
|
|
|
|
}
|
|
|
|
|
|
|
|
for col := range c.buffer {
|
|
|
|
for row := range c.buffer[col] {
|
|
|
|
cell := c.buffer[col][row]
|
|
|
|
// The image.Point{0, 0} of this canvas isn't always exactly at
|
|
|
|
// image.Point{0, 0} on the terminal.
|
|
|
|
// Depends on area assigned by the container.
|
|
|
|
offset := c.area.Min
|
|
|
|
p := image.Point{col, row}.Add(offset)
|
|
|
|
if err := t.SetCell(p, cell.Rune, cell.Opts); err != nil {
|
|
|
|
return fmt.Errorf("terminal.SetCell(%+v) => error: %v", p, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2018-03-29 02:34:20 +08:00
|
|
|
}
|