github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/rty/canvas.go (about) 1 package rty 2 3 import ( 4 "fmt" 5 6 "github.com/gdamore/tcell" 7 ) 8 9 // Canvases hold content. 10 11 type Canvas interface { 12 Size() (int, int) 13 SetContent(x int, y int, mainc rune, combc []rune, style tcell.Style) 14 Close() (int, int) 15 GetContent(x, y int) (mainc rune, combc []rune, style tcell.Style, width int) 16 } 17 18 func totalHeight(canvases []Canvas) int { 19 total := 0 20 for _, c := range canvases { 21 _, h := c.Size() 22 total += h 23 } 24 return total 25 } 26 27 // Implementations below 28 type cell struct { 29 ch rune 30 style tcell.Style 31 } 32 33 type TempCanvas struct { 34 width int 35 height int 36 cells [][]cell 37 style tcell.Style 38 handler ErrorHandler 39 } 40 41 var _ Canvas = &TempCanvas{} 42 43 func newTempCanvas(width, height int, style tcell.Style, handler ErrorHandler) *TempCanvas { 44 c := &TempCanvas{width: width, height: height, handler: handler} 45 if height != GROW { 46 c.cells = make([][]cell, height) 47 for i := 0; i < height; i++ { 48 c.cells[i] = c.makeRow() 49 } 50 } 51 return c 52 } 53 54 func (c *TempCanvas) Size() (int, int) { 55 return c.width, c.height 56 } 57 58 func (c *TempCanvas) Close() (int, int) { 59 if c.height == GROW { 60 c.height = len(c.cells) 61 } 62 return c.width, c.height 63 } 64 65 func (c *TempCanvas) makeRow() []cell { 66 row := make([]cell, c.width) 67 for i := 0; i < c.width; i++ { 68 row[i].style = c.style 69 } 70 return row 71 } 72 73 func (c *TempCanvas) SetContent(x int, y int, mainc rune, combc []rune, style tcell.Style) { 74 if mainc == 0 { 75 mainc = ' ' 76 } 77 if x < 0 || x >= c.width || y < 0 || y >= c.height { 78 c.handler.Errorf("cell %v,%v outside canvas %v,%v", x, y, c.width, c.height) 79 return 80 } 81 82 for y >= len(c.cells) { 83 c.cells = append(c.cells, c.makeRow()) 84 } 85 86 c.cells[y][x] = cell{ch: mainc, style: style} 87 } 88 89 func (c *TempCanvas) GetContent(x, y int) (mainc rune, combc []rune, style tcell.Style, width int) { 90 if x < 0 || x >= c.width || y < 0 || y >= c.height { 91 c.handler.Errorf("cell %d, %d outside bounds %d, %d", x, y, c.width, c.height) 92 return 0, nil, tcell.StyleDefault, 1 93 } 94 95 if y >= len(c.cells) { 96 return 0, nil, tcell.StyleDefault, 1 97 } 98 99 cell := c.cells[y][x] 100 return cell.ch, nil, cell.style, 1 101 } 102 103 type SubCanvas struct { 104 del Canvas 105 startX int 106 startY int 107 width int 108 height int 109 highWater int 110 style tcell.Style 111 needsFill bool 112 handler ErrorHandler 113 } 114 115 func newSubCanvas(del Canvas, startX int, startY int, width int, height int, style tcell.Style, handler ErrorHandler) (*SubCanvas, error) { 116 _, delHeight := del.Size() 117 if height == GROW && delHeight != GROW { 118 return nil, fmt.Errorf("can't create a growing subcanvas from a non-growing subcanvas") 119 } 120 121 needsFill := true 122 delSubCanvas, ok := del.(*SubCanvas) 123 if ok { 124 // If this is a subcanvas of a subcanvas with the exact same style (or with 125 // only the foreground different), we already reset the canvas to the 126 // current style. No need to re-fill. 127 needsFill = style.Foreground(tcell.ColorDefault) != 128 delSubCanvas.style.Foreground(tcell.ColorDefault) 129 } 130 131 r := &SubCanvas{ 132 del: del, 133 startX: startX, 134 startY: startY, 135 width: width, 136 height: height, 137 highWater: -1, 138 style: style, 139 needsFill: needsFill, 140 handler: handler, 141 } 142 if needsFill { 143 r.fill(-1) 144 } 145 return r, nil 146 } 147 148 func (c *SubCanvas) Size() (int, int) { 149 return c.width, c.height 150 } 151 152 func (c *SubCanvas) Close() (int, int) { 153 if c.height == GROW { 154 c.height = c.highWater + 1 155 } 156 return c.width, c.height 157 } 158 159 func (c *SubCanvas) SetContent(x int, y int, mainc rune, combc []rune, style tcell.Style) { 160 if mainc == 0 { 161 mainc = ' ' 162 } 163 if x < 0 || x >= c.width || y < 0 || y >= c.height { 164 c.handler.Errorf("coord %d,%d is outside bounds %d,%d", x, y, c.width, c.height) 165 return 166 } 167 168 if c.height == GROW && y > c.highWater { 169 oldHighWater := c.highWater 170 c.highWater = y 171 if c.needsFill { 172 c.fill(oldHighWater) 173 } 174 } 175 c.del.SetContent(c.startX+x, c.startY+y, mainc, combc, style) 176 } 177 178 func (c *SubCanvas) fill(lastFilled int) { 179 startY := lastFilled + 1 180 maxY := c.height 181 if maxY == GROW { 182 maxY = c.highWater + 1 183 } 184 for y := startY; y < maxY; y++ { 185 for x := 0; x < c.width; x++ { 186 c.del.SetContent(c.startX+x, c.startY+y, ' ', nil, c.style) 187 } 188 } 189 } 190 191 func (c *SubCanvas) GetContent(x int, y int) (rune, []rune, tcell.Style, int) { 192 return c.del.GetContent(x, y) 193 } 194 195 type ScreenCanvas struct { 196 del tcell.Screen 197 handler ErrorHandler 198 } 199 200 var _ Canvas = &ScreenCanvas{} 201 202 func newScreenCanvas(del tcell.Screen, handler ErrorHandler) *ScreenCanvas { 203 return &ScreenCanvas{del: del, handler: handler} 204 } 205 206 func (c *ScreenCanvas) Size() (int, int) { 207 return c.del.Size() 208 } 209 210 func (c *ScreenCanvas) SetContent(x int, y int, mainc rune, combc []rune, style tcell.Style) { 211 if mainc == 0 { 212 mainc = ' ' 213 } 214 c.del.SetContent(x, y, mainc, combc, style) 215 } 216 217 func (c *ScreenCanvas) Close() (int, int) { 218 return c.del.Size() 219 } 220 221 func (c *ScreenCanvas) GetContent(x, y int) (mainc rune, combc []rune, style tcell.Style, width int) { 222 return c.del.GetContent(x, y) 223 }