...

Source file src/github.com/gdamore/tcell/v2/cell.go

Documentation: github.com/gdamore/tcell/v2

     1  // Copyright 2022 The TCell Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use file except in compliance with the License.
     5  // You may obtain a copy of the license at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tcell
    16  
    17  import (
    18  	"os"
    19  
    20  	runewidth "github.com/mattn/go-runewidth"
    21  )
    22  
    23  type cell struct {
    24  	currMain  rune
    25  	currComb  []rune
    26  	currStyle Style
    27  	lastMain  rune
    28  	lastStyle Style
    29  	lastComb  []rune
    30  	width     int
    31  }
    32  
    33  // CellBuffer represents a two dimensional array of character cells.
    34  // This is primarily intended for use by Screen implementors; it
    35  // contains much of the common code they need.  To create one, just
    36  // declare a variable of its type; no explicit initialization is necessary.
    37  //
    38  // CellBuffer is not thread safe.
    39  type CellBuffer struct {
    40  	w     int
    41  	h     int
    42  	cells []cell
    43  }
    44  
    45  // SetContent sets the contents (primary rune, combining runes,
    46  // and style) for a cell at a given location.
    47  func (cb *CellBuffer) SetContent(x int, y int,
    48  	mainc rune, combc []rune, style Style) {
    49  
    50  	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
    51  		c := &cb.cells[(y*cb.w)+x]
    52  
    53  		for i := 1; i < c.width; i++ {
    54  			cb.SetDirty(x+i, y, true)
    55  		}
    56  
    57  		c.currComb = append([]rune{}, combc...)
    58  
    59  		if c.currMain != mainc {
    60  			c.width = runewidth.RuneWidth(mainc)
    61  		}
    62  		c.currMain = mainc
    63  		c.currStyle = style
    64  	}
    65  }
    66  
    67  // GetContent returns the contents of a character cell, including the
    68  // primary rune, any combining character runes (which will usually be
    69  // nil), the style, and the display width in cells.  (The width can be
    70  // either 1, normally, or 2 for East Asian full-width characters.)
    71  func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
    72  	var mainc rune
    73  	var combc []rune
    74  	var style Style
    75  	var width int
    76  	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
    77  		c := &cb.cells[(y*cb.w)+x]
    78  		mainc, combc, style = c.currMain, c.currComb, c.currStyle
    79  		if width = c.width; width == 0 || mainc < ' ' {
    80  			width = 1
    81  			mainc = ' '
    82  		}
    83  	}
    84  	return mainc, combc, style, width
    85  }
    86  
    87  // Size returns the (width, height) in cells of the buffer.
    88  func (cb *CellBuffer) Size() (int, int) {
    89  	return cb.w, cb.h
    90  }
    91  
    92  // Invalidate marks all characters within the buffer as dirty.
    93  func (cb *CellBuffer) Invalidate() {
    94  	for i := range cb.cells {
    95  		cb.cells[i].lastMain = rune(0)
    96  	}
    97  }
    98  
    99  // Dirty checks if a character at the given location needs an
   100  // to be refreshed on the physical display.  This returns true
   101  // if the cell content is different since the last time it was
   102  // marked clean.
   103  func (cb *CellBuffer) Dirty(x, y int) bool {
   104  	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
   105  		c := &cb.cells[(y*cb.w)+x]
   106  		if c.lastMain == rune(0) {
   107  			return true
   108  		}
   109  		if c.lastMain != c.currMain {
   110  			return true
   111  		}
   112  		if c.lastStyle != c.currStyle {
   113  			return true
   114  		}
   115  		if len(c.lastComb) != len(c.currComb) {
   116  			return true
   117  		}
   118  		for i := range c.lastComb {
   119  			if c.lastComb[i] != c.currComb[i] {
   120  				return true
   121  			}
   122  		}
   123  	}
   124  	return false
   125  }
   126  
   127  // SetDirty is normally used to indicate that a cell has
   128  // been displayed (in which case dirty is false), or to manually
   129  // force a cell to be marked dirty.
   130  func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
   131  	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
   132  		c := &cb.cells[(y*cb.w)+x]
   133  		if dirty {
   134  			c.lastMain = rune(0)
   135  		} else {
   136  			if c.currMain == rune(0) {
   137  				c.currMain = ' '
   138  			}
   139  			c.lastMain = c.currMain
   140  			c.lastComb = c.currComb
   141  			c.lastStyle = c.currStyle
   142  		}
   143  	}
   144  }
   145  
   146  // Resize is used to resize the cells array, with different dimensions,
   147  // while preserving the original contents.  The cells will be invalidated
   148  // so that they can be redrawn.
   149  func (cb *CellBuffer) Resize(w, h int) {
   150  
   151  	if cb.h == h && cb.w == w {
   152  		return
   153  	}
   154  
   155  	newc := make([]cell, w*h)
   156  	for y := 0; y < h && y < cb.h; y++ {
   157  		for x := 0; x < w && x < cb.w; x++ {
   158  			oc := &cb.cells[(y*cb.w)+x]
   159  			nc := &newc[(y*w)+x]
   160  			nc.currMain = oc.currMain
   161  			nc.currComb = oc.currComb
   162  			nc.currStyle = oc.currStyle
   163  			nc.width = oc.width
   164  			nc.lastMain = rune(0)
   165  		}
   166  	}
   167  	cb.cells = newc
   168  	cb.h = h
   169  	cb.w = w
   170  }
   171  
   172  // Fill fills the entire cell buffer array with the specified character
   173  // and style.  Normally choose ' ' to clear the screen.  This API doesn't
   174  // support combining characters, or characters with a width larger than one.
   175  func (cb *CellBuffer) Fill(r rune, style Style) {
   176  	for i := range cb.cells {
   177  		c := &cb.cells[i]
   178  		c.currMain = r
   179  		c.currComb = nil
   180  		c.currStyle = style
   181  		c.width = 1
   182  	}
   183  }
   184  
   185  var runeConfig *runewidth.Condition
   186  
   187  func init() {
   188  	// The defaults for the runewidth package are poorly chosen for terminal
   189  	// applications.  We however will honor the setting in the environment if
   190  	// it is set.
   191  	if os.Getenv("RUNEWIDTH_EASTASIAN") == "" {
   192  		runewidth.DefaultCondition.EastAsianWidth = false
   193  	}
   194  
   195  	// For performance reasons, we create a lookup table.  However some users
   196  	// might be more memory conscious.  If that's you, set the TCELL_MINIMIZE
   197  	// environment variable.
   198  	if os.Getenv("TCELL_MINIMIZE") == "" {
   199  		runewidth.CreateLUT()
   200  	}
   201  }
   202  

View as plain text