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  }