v0.1.0 initial commit

This commit is contained in:
2026-07-12 18:41:05 -04:00
commit aa22225c61
69 changed files with 10971 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
package tui
// --- Centering ---
// Center returns a centered region of given size within outer
func Center(outer Region, w, h int) Region {
x := (outer.W - w) / 2
y := (outer.H - h) / 2
return outer.Sub(x, y, w, h)
}
// --- Ratio-based splitting ---
// Ratios are normalized if they don't sum to 1.0
// SplitH splits region horizontally by ratios (0.0-1.0)
func SplitH(r Region, ratios ...float64) []Region {
if len(ratios) == 0 {
return nil
}
// Normalize ratios
var sum float64
for _, ratio := range ratios {
sum += ratio
}
if sum <= 0 {
sum = 1
}
regions := make([]Region, len(ratios))
x := 0
remaining := r.W
for i, ratio := range ratios {
var w int
if i == len(ratios)-1 {
w = remaining // Last one gets remainder to avoid rounding gaps
} else {
w = int((float64(r.W) * ratio / sum) + 0.5) // Round to nearest cell
if w > remaining {
w = remaining
}
}
regions[i] = r.Sub(x, 0, w, r.H)
x += w
remaining -= w
}
return regions
}
// SplitV splits region vertically by ratios (0.0-1.0)
func SplitV(r Region, ratios ...float64) []Region {
if len(ratios) == 0 {
return nil
}
var sum float64
for _, ratio := range ratios {
sum += ratio
}
if sum <= 0 {
sum = 1
}
regions := make([]Region, len(ratios))
y := 0
remaining := r.H
for i, ratio := range ratios {
var h int
if i == len(ratios)-1 {
h = remaining
} else {
h = int(float64(r.H) * ratio / sum)
}
regions[i] = r.Sub(0, y, r.W, h)
y += h
remaining -= h
}
return regions
}
// --- Fixed-size splitting ---
// SplitHFixed splits with fixed left width, rest to right
func SplitHFixed(r Region, leftW int) (left, right Region) {
if leftW > r.W {
leftW = r.W
}
if leftW < 0 {
leftW = 0
}
left = r.Sub(0, 0, leftW, r.H)
right = r.Sub(leftW, 0, r.W-leftW, r.H)
return
}
// SplitVFixed splits with fixed top height, rest to bottom
func SplitVFixed(r Region, topH int) (top, bottom Region) {
if topH > r.H {
topH = r.H
}
if topH < 0 {
topH = 0
}
top = r.Sub(0, 0, r.W, topH)
bottom = r.Sub(0, topH, r.W, r.H-topH)
return
}
// --- Equal splitting ---
// SplitHEqual splits region into n equal-width columns
// gap specifies spacing between columns (e.g., 1 for divider lines)
// Returns n regions positioned with gaps between them
func SplitHEqual(r Region, n, gap int) []Region {
if n <= 0 {
return nil
}
if n == 1 {
return []Region{r}
}
totalGaps := gap * (n - 1)
availW := r.W - totalGaps
if availW < n {
availW = n
}
baseW := availW / n
extra := availW % n
regions := make([]Region, n)
x := 0
for i := 0; i < n; i++ {
w := baseW
if i < extra {
w++
}
regions[i] = r.Sub(x, 0, w, r.H)
x += w + gap
}
return regions
}
// SplitVEqual splits region into n equal-height rows
// gap specifies spacing between rows
func SplitVEqual(r Region, n, gap int) []Region {
if n <= 0 {
return nil
}
if n == 1 {
return []Region{r}
}
totalGaps := gap * (n - 1)
availH := r.H - totalGaps
if availH < n {
availH = n
}
baseH := availH / n
extra := availH % n
regions := make([]Region, n)
y := 0
for i := 0; i < n; i++ {
h := baseH
if i < extra {
h++
}
regions[i] = r.Sub(0, y, r.W, h)
y += h + gap
}
return regions
}
// --- Grid layout ---
// Columns calculates how many columns fit in width
func Columns(availableW, itemW, gap int) int {
if itemW <= 0 {
return 0
}
if availableW < itemW {
return 0
}
// First item has no gap, subsequent items need gap + itemW
cols := 1 + (availableW-itemW)/(itemW+gap)
if cols < 0 {
cols = 0
}
return cols
}
// GridLayout returns a grid of equally sized regions
func GridLayout(r Region, cols, rows, gapX, gapY int) []Region {
if cols <= 0 || rows <= 0 {
return nil
}
cellW := (r.W - gapX*(cols-1)) / cols
cellH := (r.H - gapY*(rows-1)) / rows
if cellW < 1 {
cellW = 1
}
if cellH < 1 {
cellH = 1
}
regions := make([]Region, cols*rows)
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
x := col * (cellW + gapX)
y := row * (cellH + gapY)
regions[row*cols+col] = r.Sub(x, y, cellW, cellH)
}
}
return regions
}
// --- Divider positioning ---
// DividerPositions returns X coordinates for vertical dividers between equal columns
// Use with VLine to draw dividers in gaps created by SplitHEqual
func DividerPositions(regionW, n, gap int) []int {
if n <= 1 || gap <= 0 {
return nil
}
totalGaps := gap * (n - 1)
availW := regionW - totalGaps
if availW < n {
availW = n
}
baseW := availW / n
extra := availW % n
positions := make([]int, n-1)
x := 0
for i := 0; i < n-1; i++ {
w := baseW
if i < extra {
w++
}
x += w
positions[i] = x
x += gap
}
return positions
}
// HDividerPositions returns Y coordinates for horizontal dividers between equal rows
func HDividerPositions(regionH, n, gap int) []int {
if n <= 1 || gap <= 0 {
return nil
}
totalGaps := gap * (n - 1)
availH := regionH - totalGaps
if availH < n {
availH = n
}
baseH := availH / n
extra := availH % n
positions := make([]int, n-1)
y := 0
for i := 0; i < n-1; i++ {
h := baseH
if i < extra {
h++
}
y += h
positions[i] = y
y += gap
}
return positions
}
// --- Responsive utilities ---
// FitOrScroll returns true if content exceeds available height
func FitOrScroll(contentH, availableH int) bool {
return contentH > availableH
}
// Breakpoints should be in descending order
// BreakpointH returns index of first breakpoint <= w, returns len(breakpoints) if w is less than all breakpoints
func BreakpointH(w int, breakpoints ...int) int {
for i, bp := range breakpoints {
if w >= bp {
return i
}
}
return len(breakpoints)
}
// BreakpointV returns index of first breakpoint <= h
func BreakpointV(h int, breakpoints ...int) int {
return BreakpointH(h, breakpoints...)
}