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 / sheet / borders.go
borders.go 9.7K
1package sheet23// Borders, as Sheets' Format > Borders. Each cell keeps the line on each4// of its four edges in its style, so borders fall back on rows and5// columns, travel with copies and go with Clear formatting as the rest6// of the style does. Neighbors share an edge, which shows the heavier of7// the lines either keeps; setting an edge of a range from inside clears8// the facing edge of the neighbor outside, so the last change shows.910// Line is how an edge is drawn.11type Line uint81213const (14 LineNone Line = iota15 LineThin16 LineThick17 LineDouble18)1920var lineNames = [...]string{"", "thin", "thick", "double"}2122// String returns the line's name as stored in files.23func (l Line) String() string {24 if int(l) < len(lineNames) {25 return lineNames[l]26 }27 return ""28}2930// ParseLine is the inverse of Line.String.31func ParseLine(s string) (Line, bool) {32 for i, n := range lineNames {33 if n == s {34 return Line(i), true35 }36 }37 return LineNone, false38}3940// Heavier is the line drawn where two edges meet: double over thick over41// thin.42func Heavier(a, b Line) Line { return max(a, b) }4344// Borders are the lines on a cell's four edges, two bits each in the45// low byte, and their colors, four bits each above: ColorNone draws in46// the ink of the text (see Color and theme.CellBorder).47type Borders uint324849// Edge is one of a cell's edges.50type Edge uint85152const (53 EdgeTop Edge = iota54 EdgeBottom55 EdgeLeft56 EdgeRight57)5859// BordersOf is the borders with these lines.60func BordersOf(top, bottom, left, right Line) Borders {61 return Borders(0).With(EdgeTop, top).With(EdgeBottom, bottom).With(EdgeLeft, left).With(EdgeRight, right)62}6364// Line is the line on edge e.65func (b Borders) Line(e Edge) Line { return Line(b >> (2 * e) & 3) }6667// Color is the color of edge e's line.68func (b Borders) Color(e Edge) Color { return Color(b >> (8 + 4*e) & 15) }6970// Stroke is edge e's line and color.71func (b Borders) Stroke(e Edge) Stroke { return Stroke{b.Line(e), b.Color(e)} }7273// With is b with edge e drawn with l, in the color it had; no line has74// no color.75func (b Borders) With(e Edge, l Line) Borders {76 b = b&^(3<<(2*e)) | Borders(l&3)<<(2*e)77 if l == LineNone {78 b = b.WithColor(e, ColorNone)79 }80 return b81}8283// WithColor is b with edge e's line in color c.84func (b Borders) WithColor(e Edge, c Color) Borders {85 return b&^(15<<(8+4*e)) | Borders(c&15)<<(8+4*e)86}8788// WithStroke is b with edge e drawn as st.89func (b Borders) WithStroke(e Edge, st Stroke) Borders {90 return b.With(e, st.Line).WithColor(e, st.Color)91}9293// Stroke is a line and its color.94type Stroke struct {95 Line Line96 Color Color97}9899// heavier is the stroke drawn where a and b share an edge: the heavier100// line, and a's when they are as heavy.101func heavier(a, b Stroke) Stroke {102 if b.Line > a.Line {103 return b104 }105 return a106}107108func (b Borders) Top() Line { return b.Line(EdgeTop) }109func (b Borders) Bottom() Line { return b.Line(EdgeBottom) }110func (b Borders) Left() Line { return b.Line(EdgeLeft) }111func (b Borders) Right() Line { return b.Line(EdgeRight) }112113// IsZero reports whether no edge has a line.114func (b Borders) IsZero() bool { return b == 0 }115116// BorderKind is which edges of a range Format > Borders sets.117type BorderKind uint8118119const (120 BorderAll BorderKind = iota // every edge of every cell121 BorderOuter // the range's outline122 BorderInner // the edges between its cells123 BorderTop // its top edge124 BorderBottom //125 BorderLeft //126 BorderRight //127 BorderNone // no edges: removes every line128)129130var borderKindNames = [...]string{"all", "outer", "inner", "top", "bottom", "left", "right", "none"}131132// String names the kind, as undo labels and macros write it.133func (k BorderKind) String() string {134 if int(k) < len(borderKindNames) {135 return borderKindNames[k]136 }137 return ""138}139140// ParseBorderKind is the inverse of BorderKind.String.141func ParseBorderKind(s string) (BorderKind, bool) {142 for i, n := range borderKindNames {143 if n == s {144 return BorderKind(i), true145 }146 }147 return 0, false148}149150// edgeSet is which of a cell's edges a change sets.151type edgeSet struct{ top, bottom, left, right bool }152153// edges are the edges k sets on the cell at a within r. Along the axis154// r covers whole (whole columns run down every row), every cell counts155// as inside, and so do lines (see lineAddr).156func (k BorderKind) edges(r Rect, a Addr) edgeSet {157 firstRow, lastRow := a.Row >= 0 && a.Row == r.From.Row, a.Row >= 0 && a.Row == r.To.Row158 firstCol, lastCol := a.Col >= 0 && a.Col == r.From.Col, a.Col >= 0 && a.Col == r.To.Col159 if r.AllRows() {160 firstRow, lastRow = false, false161 }162 if r.AllCols() {163 firstCol, lastCol = false, false164 }165 switch k {166 case BorderAll, BorderNone:167 return edgeSet{true, true, true, true}168 case BorderOuter:169 return edgeSet{firstRow, lastRow, firstCol, lastCol}170 case BorderInner:171 return edgeSet{!firstRow, !lastRow, !firstCol, !lastCol}172 case BorderTop:173 return edgeSet{top: firstRow}174 case BorderBottom:175 return edgeSet{bottom: lastRow}176 case BorderLeft:177 return edgeSet{left: firstCol}178 }179 return edgeSet{right: lastCol}180}181182// apply sets the edges of e on b to st.183func (e edgeSet) apply(b *Borders, st Stroke) {184 for edge, set := range [...]bool{EdgeTop: e.top, EdgeBottom: e.bottom, EdgeLeft: e.left, EdgeRight: e.right} {185 if set {186 *b = b.WithStroke(Edge(edge), st)187 }188 }189}190191// SetBorders draws the edges k names in r with line l, or removes every192// line of r for BorderNone, as one undo step. Neighbors outside r lose193// the lines on the edges they share with the ones set.194func (s *Sheet) SetBorders(r Rect, k BorderKind, l Line) {195 s.SetBorderStroke(r, k, Stroke{Line: l})196}197198// SetBorderStroke is SetBorders with lines of a color. An outline of199// whole columns or rows draws the sheet's first and last edges too.200func (s *Sheet) SetBorderStroke(r Rect, k BorderKind, st Stroke) {201 label := k.String() + " border " + r.String() // "top border B2:C4"202 if k <= BorderInner {203 label = k.String() + " borders " + r.String()204 }205 if st.Color != ColorNone {206 label += " in " + st.Color.String()207 }208 if k == BorderNone {209 st, label = Stroke{}, "remove borders from "+r.String()210 }211 s.change(label, r, func() {212 s.eachFormat(r, true, func(a Addr, f *lineFmt) {213 k.edges(r, a).apply(&f.Style.Borders, st)214 })215 s.sheetEdges(r, k, st)216 s.clearFacing(r, k)217 })218}219220// sheetEdges draws the edges of whole columns or rows that lie on the221// sheet's own edges, which k.edges leaves out along the axis they cover222// whole: the top of row 1 and the bottom of the last row under whole223// columns, the left of column A and the right of the last column beside224// whole rows. Across the whole sheet those are the lines of its first225// and last rows and columns; otherwise the cells along them.226func (s *Sheet) sheetEdges(r Rect, k BorderKind, st Stroke) {227 var e edgeSet228 switch k {229 case BorderOuter:230 e = edgeSet{true, true, true, true}231 case BorderTop, BorderBottom, BorderLeft, BorderRight:232 e = edgeSet{top: k == BorderTop, bottom: k == BorderBottom, left: k == BorderLeft, right: k == BorderRight}233 default:234 return235 }236 last := Addr{Col: MaxCols - 1, Row: MaxRows - 1}237 set := func(on bool, n Rect, edge edgeSet) {238 if on {239 s.eachFormat(n, true, func(_ Addr, f *lineFmt) { edge.apply(&f.Style.Borders, st) })240 }241 }242 if r.AllRows() {243 set(e.top, Rect{From: Addr{Col: r.From.Col}, To: Addr{Col: r.To.Col}}, edgeSet{top: true})244 set(e.bottom, Rect{From: Addr{Col: r.From.Col, Row: last.Row}, To: Addr{Col: r.To.Col, Row: last.Row}}, edgeSet{bottom: true})245 }246 if r.AllCols() {247 set(e.left, Rect{From: Addr{Row: r.From.Row}, To: Addr{Row: r.To.Row}}, edgeSet{left: true})248 set(e.right, Rect{From: Addr{Col: last.Col, Row: r.From.Row}, To: Addr{Col: last.Col, Row: r.To.Row}}, edgeSet{right: true})249 }250}251252// clearFacing removes the lines of the neighbors of r on the edges that253// setting k on r draws.254func (s *Sheet) clearFacing(r Rect, k BorderKind) {255 outer := k.edges(r, r.From)256 last := k.edges(r, r.To)257 outer.bottom, outer.right = last.bottom, last.right258 strip := func(from, to Addr, e edgeSet) {259 n := Rect{From: from, To: to}260 if !n.From.Valid() || !n.To.Valid() {261 return262 }263 s.eachFormat(n, true, func(_ Addr, f *lineFmt) { e.apply(&f.Style.Borders, Stroke{}) })264 }265 if outer.top && !r.AllRows() {266 strip(Addr{Col: r.From.Col, Row: r.From.Row - 1}, Addr{Col: r.To.Col, Row: r.From.Row - 1}, edgeSet{bottom: true})267 }268 if outer.bottom && !r.AllRows() {269 strip(Addr{Col: r.From.Col, Row: r.To.Row + 1}, Addr{Col: r.To.Col, Row: r.To.Row + 1}, edgeSet{top: true})270 }271 if outer.left && !r.AllCols() {272 strip(Addr{Col: r.From.Col - 1, Row: r.From.Row}, Addr{Col: r.From.Col - 1, Row: r.To.Row}, edgeSet{right: true})273 }274 if outer.right && !r.AllCols() {275 strip(Addr{Col: r.To.Col + 1, Row: r.From.Row}, Addr{Col: r.To.Col + 1, Row: r.To.Row}, edgeSet{left: true})276 }277}278279// EdgeAbove is the line on the edge between the cell at a and the one280// above it: the heavier of their facing lines.281func (s *Sheet) EdgeAbove(a Addr) Line { return s.StrokeAbove(a).Line }282283// EdgeLeft is the line on the edge between the cell at a and the one to284// its left.285func (s *Sheet) EdgeLeft(a Addr) Line { return s.StrokeLeft(a).Line }286287// StrokeAbove is EdgeAbove with its color: the heavier line's, or the288// cell's own where the lines are as heavy.289func (s *Sheet) StrokeAbove(a Addr) Stroke {290 st := s.CellStyle(a).Borders.Stroke(EdgeTop)291 if a.Row > 0 {292 st = heavier(st, s.CellStyle(Addr{Col: a.Col, Row: a.Row - 1}).Borders.Stroke(EdgeBottom))293 }294 return st295}296297// StrokeLeft is EdgeLeft with its color.298func (s *Sheet) StrokeLeft(a Addr) Stroke {299 st := s.CellStyle(a).Borders.Stroke(EdgeLeft)300 if a.Col > 0 {301 st = heavier(st, s.CellStyle(Addr{Col: a.Col - 1, Row: a.Row}).Borders.Stroke(EdgeRight))302 }303 return st304}