v0.1.0 initial commit
This commit is contained in:
+309
@@ -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...)
|
||||
}
|
||||
Reference in New Issue
Block a user