cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / ui / panes.go
panes.go 3.5K
1package ui23import (4 "strconv"56 "src.rest/cat/012/internal/sheet"7)89// The viewport: frozen rows and columns stay on screen at the top and10// left while the rest scrolls, split from it by a thin line like a tmux11// pane border, and rows the filter hides are skipped. top and left12// are the first scrolling row and column, never inside the frozen area.13// Everything that maps between rows or columns and the screen goes14// through here, so drawing, navigation and the mouse agree.1516// frozen returns the frozen rows and columns that fit on screen: frozen17// panes too big for the window are shown only in part, leaving room for18// at least one scrolling row and column.19func (g *grid) frozen() (rows, cols int) {20 rows, cols = g.sheet.Frozen()21 for rows > 0 && g.frozenLines(rows)+1 >= g.visibleRows() {22 rows--23 }24 w := 025 for c := range cols {26 w += g.sheet.ColWidth(c)27 if g.hdrW()+w+1+3 > g.width {28 cols = c29 break30 }31 }32 return rows, cols33}3435// hdrW is the width of the row numbers: the widest number on screen with36// a space either side, and at least minRowHdrW, so it widens past row37// 9999 as Excel's does.38func (g *grid) hdrW() int {39 last := g.top + g.visibleRows()40 if g.sheet.HiddenRows() > 0 {41 last = g.top42 for range g.visibleRows() {43 r, ok := g.sheet.NextShownRow(last, 1)44 if !ok {45 break46 }47 last = r48 }49 last++50 }51 return max(minRowHdrW, len(strconv.Itoa(min(last, sheet.MaxRows)))+2)52}5354// scrollX is the screen x where the scrolling columns start: after the55// row numbers, the frozen columns and their divider.56func (g *grid) scrollX() int {57 _, fc := g.frozen()58 x := g.hdrW()59 for c := range fc {60 x += g.sheet.ColWidth(c)61 }62 if fc > 0 {63 x++64 }65 return x66}6768// clampView keeps the scrolling area out of the frozen panes, e.g. after69// freezing more rows than were scrolled past.70func (g *grid) clampView() {71 fr, fc := g.frozen()72 g.top = clamp(g.top, fr, sheet.MaxRows-1)73 g.left = clamp(g.left, fc, sheet.MaxCols-1)74}7576func (g *grid) visibleRows() int {77 return max(g.height-gridTop-1, 1)78}7980// visibleCols returns how many whole scrolling columns fit starting at81// left.82func (g *grid) visibleCols(left int) int {83 n, w := 0, g.scrollX()84 for c := left; c < sheet.MaxCols; c++ {85 w += g.sheet.ColWidth(c)86 if w > g.width {87 break88 }89 n++90 }91 return max(n, 1)92}9394// colSpan returns the visible column under x and the x where it starts:95// a frozen column or a scrolling one, but not the divider between them.96func (g *grid) colSpan(x int) (col, start int, ok bool) {97 _, fc := g.frozen()98 cx := g.hdrW()99 for c := 0; c < fc; c++ {100 w := g.sheet.ColWidth(c)101 if x >= cx && x < cx+w {102 return c, cx, true103 }104 cx += w105 }106 cx = g.scrollX()107 for c := g.left; c < sheet.MaxCols && cx < g.width; c++ {108 w := g.sheet.ColWidth(c)109 if x >= cx && x < cx+w {110 return c, cx, true111 }112 cx += w113 }114 return 0, 0, false115}116117// colStart returns the screen x where column c starts, which may be off118// screen (or under the frozen columns, for a scrolling column left of119// left).120func (g *grid) colStart(c int) int {121 _, fc := g.frozen()122 if c < fc {123 x := g.hdrW()124 for k := range c {125 x += g.sheet.ColWidth(k)126 }127 return x128 }129 x := g.scrollX()130 if c >= g.left {131 for k := g.left; k < c; k++ {132 x += g.sheet.ColWidth(k)133 }134 return x135 }136 for k := c; k < g.left; k++ {137 x -= g.sheet.ColWidth(k)138 }139 return x140}141142// clampBox keeps a w by h box at x, y on screen, shifting it left and up143// as needed.144func (g *grid) clampBox(x, y, w, h int) (int, int) {145 return max(min(x, g.width-w), 0), max(min(y, g.height-h), 0)146}