1// Copyright 2019 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
15package tcell
16
17import (
18	runewidth "github.com/mattn/go-runewidth"
19)
20
21type cell struct {
22	currMain  rune
23	currComb  []rune
24	currStyle Style
25	lastMain  rune
26	lastStyle Style
27	lastComb  []rune
28	width     int
29}
30
31// CellBuffer represents a two dimensional array of character cells.
32// This is primarily intended for use by Screen implementors; it
33// contains much of the common code they need.  To create one, just
34// declare a variable of its type; no explicit initialization is necessary.
35//
36// CellBuffer is not thread safe.
37type CellBuffer struct {
38	w     int
39	h     int
40	cells []cell
41}
42
43// SetContent sets the contents (primary rune, combining runes,
44// and style) for a cell at a given location.
45func (cb *CellBuffer) SetContent(x int, y int,
46	mainc rune, combc []rune, style Style) {
47
48	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
49		c := &cb.cells[(y*cb.w)+x]
50
51		c.currComb = append([]rune{}, combc...)
52
53		if c.currMain != mainc {
54			c.width = runewidth.RuneWidth(mainc)
55		}
56		c.currMain = mainc
57		c.currStyle = style
58	}
59}
60
61// GetContent returns the contents of a character cell, including the
62// primary rune, any combining character runes (which will usually be
63// nil), the style, and the display width in cells.  (The width can be
64// either 1, normally, or 2 for East Asian full-width characters.)
65func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
66	var mainc rune
67	var combc []rune
68	var style Style
69	var width int
70	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
71		c := &cb.cells[(y*cb.w)+x]
72		mainc, combc, style = c.currMain, c.currComb, c.currStyle
73		if width = c.width; width == 0 || mainc < ' ' {
74			width = 1
75			mainc = ' '
76		}
77	}
78	return mainc, combc, style, width
79}
80
81// Size returns the (width, height) in cells of the buffer.
82func (cb *CellBuffer) Size() (int, int) {
83	return cb.w, cb.h
84}
85
86// Invalidate marks all characters within the buffer as dirty.
87func (cb *CellBuffer) Invalidate() {
88	for i := range cb.cells {
89		cb.cells[i].lastMain = rune(0)
90	}
91}
92
93// Dirty checks if a character at the given location needs an
94// to be refreshed on the physical display.  This returns true
95// if the cell content is different since the last time it was
96// marked clean.
97func (cb *CellBuffer) Dirty(x, y int) bool {
98	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
99		c := &cb.cells[(y*cb.w)+x]
100		if c.lastMain == rune(0) {
101			return true
102		}
103		if c.lastMain != c.currMain {
104			return true
105		}
106		if c.lastStyle != c.currStyle {
107			return true
108		}
109		if len(c.lastComb) != len(c.currComb) {
110			return true
111		}
112		for i := range c.lastComb {
113			if c.lastComb[i] != c.currComb[i] {
114				return true
115			}
116		}
117	}
118	return false
119}
120
121// SetDirty is normally used to indicate that a cell has
122// been displayed (in which case dirty is false), or to manually
123// force a cell to be marked dirty.
124func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
125	if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
126		c := &cb.cells[(y*cb.w)+x]
127		if dirty {
128			c.lastMain = rune(0)
129		} else {
130			if c.currMain == rune(0) {
131				c.currMain = ' '
132			}
133			c.lastMain = c.currMain
134			c.lastComb = c.currComb
135			c.lastStyle = c.currStyle
136		}
137	}
138}
139
140// Resize is used to resize the cells array, with different dimensions,
141// while preserving the original contents.  The cells will be invalidated
142// so that they can be redrawn.
143func (cb *CellBuffer) Resize(w, h int) {
144
145	if cb.h == h && cb.w == w {
146		return
147	}
148
149	newc := make([]cell, w*h)
150	for y := 0; y < h && y < cb.h; y++ {
151		for x := 0; x < w && x < cb.w; x++ {
152			oc := &cb.cells[(y*cb.w)+x]
153			nc := &newc[(y*w)+x]
154			nc.currMain = oc.currMain
155			nc.currComb = oc.currComb
156			nc.currStyle = oc.currStyle
157			nc.width = oc.width
158			nc.lastMain = rune(0)
159		}
160	}
161	cb.cells = newc
162	cb.h = h
163	cb.w = w
164}
165
166// Fill fills the entire cell buffer array with the specified character
167// and style.  Normally choose ' ' to clear the screen.  This API doesn't
168// support combining characters, or characters with a width larger than one.
169func (cb *CellBuffer) Fill(r rune, style Style) {
170	for i := range cb.cells {
171		c := &cb.cells[i]
172		c.currMain = r
173		c.currComb = nil
174		c.currStyle = style
175		c.width = 1
176	}
177}
178