Files
terminal/tui/region.go
T

144 lines
3.3 KiB
Go

package tui
import (
"github.com/lixenwraith/color"
"github.com/lixenwraith/terminal"
)
// Region represents a rectangular area within a cell buffer
// All coordinates are relative to the region's origin
type Region struct {
Cells []terminal.Cell
TotalW int // Total width of the underlying cell buffer
X, Y int // Absolute position in cell buffer
W, H int // Region dimensions
}
// NewRegion creates a region referencing a cell slice with bounds
func NewRegion(cells []terminal.Cell, totalW, x, y, w, h int) Region {
return Region{
Cells: cells,
TotalW: totalW,
X: x,
Y: y,
W: w,
H: h,
}
}
// Sub returns a nested region with coordinates relative to parent, result is clipped to parent bounds
func (r Region) Sub(x, y, w, h int) Region {
// Clip to parent bounds
if x < 0 {
w += x
x = 0
}
if y < 0 {
h += y
y = 0
}
if x+w > r.W {
w = r.W - x
}
if y+h > r.H {
h = r.H - y
}
if w < 0 {
w = 0
}
if h < 0 {
h = 0
}
return Region{
Cells: r.Cells,
TotalW: r.TotalW,
X: r.X + x,
Y: r.Y + y,
W: w,
H: h,
}
}
// Inset returns a region shrunk by n cells on all sides
func (r Region) Inset(n int) Region {
return r.Sub(n, n, r.W-2*n, r.H-2*n)
}
// Cell sets a single cell with bounds checking.
// A zero-value bg (color.RGB{}) is transparent: the existing cell's
// background is preserved. Establish a base background first (Fill, or
// pre-initialized cell buffer). For a literal black background use
// CellOpaque or Fill(color.RGB{}).
func (r Region) Cell(x, y int, ch rune, fg, bg color.RGB, attr terminal.Attr) {
if x < 0 || x >= r.W || y < 0 || y >= r.H {
return
}
absX := r.X + x
absY := r.Y + y
// Bounds check against the physical buffer dimensions
if uint(absX) >= uint(r.TotalW) {
return
}
idx := absY*r.TotalW + absX
// Single bounds check for the backing slice
if uint(idx) < uint(len(r.Cells)) {
if bg == (color.RGB{}) {
bg = r.Cells[idx].Bg // transparent: inherit current background
}
r.Cells[idx] = terminal.Cell{Rune: ch, Fg: fg, Bg: bg, Attrs: attr}
}
}
// CellOpaque sets a single cell writing bg verbatim, including zero (black).
// Used by base-layer operations (Fill) that must be able to write any color.
func (r Region) CellOpaque(x, y int, ch rune, fg, bg color.RGB, attr terminal.Attr) {
if x < 0 || x >= r.W || y < 0 || y >= r.H {
return
}
absX := r.X + x
absY := r.Y + y
if uint(absX) >= uint(r.TotalW) {
return
}
idx := absY*r.TotalW + absX
if uint(idx) < uint(len(r.Cells)) {
r.Cells[idx] = terminal.Cell{Rune: ch, Fg: fg, Bg: bg, Attrs: attr}
}
}
// Fill fills entire region with background color.
// Opaque by definition: Fill establishes the base layer, so bg is written
// verbatim (including zero/black) rather than treated as transparent.
func (r Region) Fill(bg color.RGB) {
for y := 0; y < r.H; y++ {
for x := 0; x < r.W; x++ {
r.CellOpaque(x, y, ' ', color.RGB{}, bg, terminal.AttrNone)
}
}
}
// Clear fills region with spaces and zero colors
func (r Region) Clear() {
r.Fill(color.RGB{})
}
// Width returns region width
func (r Region) Width() int {
return r.W
}
// Height returns region height
func (r Region) Height() int {
return r.H
}
// Bounds returns absolute position and dimensions
func (r Region) Bounds() (x, y, w, h int) {
return r.X, r.Y, r.W, r.H
}