func (gd Grid) Contains(p Point) bool {
	return p.Add(gd.Rg.Min).In(gd.Rg)
}
Contains returns true if the given relative position is within the grid.
package gruid
import (
	"fmt"
	"strings"
	"time"
)
type AttrMask uint32
const AttrsDefault AttrMask = 0
type Color uint32
const ColorDefault Color = 0
type Cell struct {
	Rune  rune   
	Style Style  
}
func (c Cell) WithRune(r rune) Cell {
	c.Rune = r
	return c
}
func (c Cell) WithStyle(st Style) Cell {
	c.Style = st
	return c
}
type Style struct {
	Fg    Color     
	Bg    Color     
	Attrs AttrMask  
}
func (st Style) WithFg(cl Color) Style {
	st.Fg = cl
	return st
}
func (st Style) WithBg(cl Color) Style {
	st.Bg = cl
	return st
}
func (st Style) WithAttrs(attrs AttrMask) Style {
	st.Attrs = attrs
	return st
}
type Point struct {
	X int
	Y int
}
func (p Point) String() string {
	return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
func (p Point) Shift(x, y int) Point {
	return Point{X: p.X + x, Y: p.Y + y}
}
func (p Point) Add(q Point) Point {
	return Point{X: p.X + q.X, Y: p.Y + q.Y}
}
func (p Point) Sub(q Point) Point {
	return Point{X: p.X - q.X, Y: p.Y - q.Y}
}
func (p Point) In(rg Range) bool {
	return p.X >= rg.Min.X && p.X < rg.Max.X && p.Y >= rg.Min.Y && p.Y < rg.Max.Y
}
func (p Point) Mul(k int) Point {
	return Point{X: p.X * k, Y: p.Y * k}
}
func (p Point) Div(k int) Point {
	return Point{X: p.X / k, Y: p.Y / k}
}
type Range struct {
	Min, Max Point
}
func NewRange(x0, y0, x1, y1 int) Range {
	if x1 < x0 {
		x0, x1 = x1, x0
	}
	if y1 < y0 {
		y0, y1 = y1, y0
	}
	return Range{Min: Point{X: x0, Y: y0}, Max: Point{X: x1, Y: y1}}
}
func (rg Range) String() string {
	return fmt.Sprintf("%s-%s", rg.Min, rg.Max)
}
func (rg Range) Size() Point {
	return rg.Max.Sub(rg.Min)
}
func (rg Range) Shift(x0, y0, x1, y1 int) Range {
	rg = Range{Min: rg.Min.Shift(x0, y0), Max: rg.Max.Shift(x1, y1)}
	if rg.Empty() {
		return Range{}
	}
	return rg
}
func (rg Range) Line(y int) Range {
	if rg.Min.Shift(0, y).In(rg) {
		rg.Min.Y = rg.Min.Y + y
		rg.Max.Y = rg.Min.Y + 1
	} else {
		rg = Range{}
	}
	return rg
}
func (rg Range) Lines(y0, y1 int) Range {
	nrg := rg
	nrg.Min.Y = rg.Min.Y + y0
	nrg.Max.Y = rg.Min.Y + y1
	return rg.Intersect(nrg)
}
func (rg Range) Column(x int) Range {
	if rg.Min.Shift(x, 0).In(rg) {
		rg.Min.X = rg.Min.X + x
		rg.Max.X = rg.Min.X + 1
	} else {
		rg = Range{}
	}
	return rg
}
func (rg Range) Columns(x0, x1 int) Range {
	nrg := rg
	nrg.Min.X = rg.Min.X + x0
	nrg.Max.X = rg.Min.X + x1
	return rg.Intersect(nrg)
}
func (rg Range) Empty() bool {
	return rg.Min.X >= rg.Max.X || rg.Min.Y >= rg.Max.Y
}
func (rg Range) Eq(r Range) bool {
	return rg == r || rg.Empty() && r.Empty()
}
func (rg Range) Sub(p Point) Range {
	rg.Max = rg.Max.Sub(p)
	rg.Min = rg.Min.Sub(p)
	return rg
}
func (rg Range) Add(p Point) Range {
	rg.Max = rg.Max.Add(p)
	rg.Min = rg.Min.Add(p)
	return rg
}
func (rg Range) RelMsg(msg Msg) Msg {
	if msg, ok := msg.(MsgMouse); ok {
		msg.P = msg.P.Sub(rg.Min)
		return msg
	}
	return msg
}
func (rg Range) Intersect(r Range) Range {
	if rg.Max.X > r.Max.X {
		rg.Max.X = r.Max.X
	}
	if rg.Max.Y > r.Max.Y {
		rg.Max.Y = r.Max.Y
	}
	if rg.Min.X < r.Min.X {
		rg.Min.X = r.Min.X
	}
	if rg.Min.Y < r.Min.Y {
		rg.Min.Y = r.Min.Y
	}
	if rg.Min.X >= rg.Max.X || rg.Min.Y >= rg.Max.Y {
		return Range{}
	}
	return rg
}
func (rg Range) Union(r Range) Range {
	if rg.Max.X < r.Max.X {
		rg.Max.X = r.Max.X
	}
	if rg.Max.Y < r.Max.Y {
		rg.Max.Y = r.Max.Y
	}
	if rg.Min.X > r.Min.X {
		rg.Min.X = r.Min.X
	}
	if rg.Min.Y > r.Min.Y {
		rg.Min.Y = r.Min.Y
	}
	return rg
}
func (rg Range) Overlaps(r Range) bool {
	return !rg.Intersect(r).Empty()
}
func (rg Range) In(r Range) bool {
	return rg.Intersect(r) == rg
}
func (rg Range) Iter(fn func(Point)) {
	for y := rg.Min.Y; y < rg.Max.Y; y++ {
		for x := rg.Min.X; x < rg.Max.X; x++ {
			p := Point{X: x, Y: y}
			fn(p)
		}
	}
}
type Grid struct {
	innerGrid
}
type innerGrid struct {
	Ug *grid  
	Rg Range  
}
type grid struct {
	Cells  []Cell
	Width  int
	Height int
}
type Frame struct {
	Time   time.Time    
	Cells  []FrameCell  
	Width  int          
	Height int          
}
type FrameCell struct {
	Cell Cell   
	P    Point  
}
func NewGrid(w, h int) Grid {
	gd := Grid{}
	gd.Ug = &grid{}
	if w < 0 || h < 0 {
		panic(fmt.Sprintf("negative dimensions: NewGrid(%d,%d)", w, h))
	}
	gd.Rg.Max = Point{w, h}
	gd.Ug.Width = w
	gd.Ug.Height = h
	gd.Ug.Cells = make([]Cell, w*h)
	gd.Fill(Cell{Rune: ' '})
	return gd
}
func (gd Grid) String() string {
	b := strings.Builder{}
	it := gd.Iterator()
	for it.Next() {
		b.WriteRune(it.Cell().Rune)
		p := it.P()
		if p.X == gd.Rg.Max.X-1 {
			b.WriteRune('\n')
		}
	}
	return b.String()
}
func (gd Grid) Bounds() Range {
	return gd.Rg
}
func (gd Grid) Range() Range {
	return gd.Rg.Sub(gd.Rg.Min)
}
func (gd Grid) Slice(rg Range) Grid {
	if rg.Min.X < 0 {
		rg.Min.X = 0
	}
	if rg.Min.Y < 0 {
		rg.Min.Y = 0
	}
	max := gd.Rg.Size()
	if rg.Max.X > max.X {
		rg.Max.X = max.X
	}
	if rg.Max.Y > max.Y {
		rg.Max.Y = max.Y
	}
	min := gd.Rg.Min
	rg.Min = rg.Min.Add(min)
	rg.Max = rg.Max.Add(min)
	return Grid{innerGrid{Ug: gd.Ug, Rg: rg}}
}
func (gd Grid) Size() Point {
	return gd.Rg.Size()
}
func (gd Grid) Resize(w, h int) Grid {
	max := gd.Size()
	ow, oh := max.X, max.Y
	if ow == w && oh == h {
		return gd
	}
	if w <= 0 || h <= 0 {
		gd.Rg.Max = gd.Rg.Min
		return gd
	}
	if gd.Ug == nil {
		gd.Ug = &grid{}
	}
	gd.Rg.Max = gd.Rg.Min.Shift(w, h)
	if w+gd.Rg.Min.X > gd.Ug.Width || h+gd.Rg.Min.Y > gd.Ug.Height {
		ngd := NewGrid(w+gd.Rg.Min.X, h+gd.Rg.Min.Y)
		ngd.Copy(Grid{innerGrid{Ug: gd.Ug, Rg: NewRange(0, 0, gd.Ug.Width, gd.Ug.Height)}})
		*gd.Ug = *ngd.Ug
	}
	return gd
}