309 lines
5.7 KiB
Go
309 lines
5.7 KiB
Go
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...)
|
|
} |