Skip to content

Commit 7d504a2

Browse files
committed
Move termgridrenderer to its own file
1 parent 4447408 commit 7d504a2

File tree

3 files changed

+289
-290
lines changed

3 files changed

+289
-290
lines changed

internal/widget/renderer.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package widget
2+
3+
import (
4+
"context"
5+
"image/color"
6+
"math"
7+
"strconv"
8+
"time"
9+
10+
"fyne.io/fyne/v2"
11+
"fyne.io/fyne/v2/canvas"
12+
"fyne.io/fyne/v2/theme"
13+
"fyne.io/fyne/v2/widget"
14+
)
15+
16+
type termGridRenderer struct {
17+
text *TermGrid
18+
19+
cols, rows int
20+
21+
cellSize fyne.Size
22+
objects []fyne.CanvasObject
23+
current fyne.Canvas
24+
blink bool
25+
shouldBlink bool
26+
tickerCancel context.CancelFunc
27+
}
28+
29+
func (t *termGridRenderer) appendTextCell(str rune) {
30+
text := canvas.NewText(string(str), theme.ForegroundColor())
31+
text.TextStyle.Monospace = true
32+
33+
bg := canvas.NewRectangle(color.Transparent)
34+
t.objects = append(t.objects, bg, text)
35+
}
36+
37+
func (t *termGridRenderer) setCellRune(str rune, pos int, style widget.TextGridStyle) {
38+
if str == 0 {
39+
str = ' '
40+
}
41+
fg := theme.ForegroundColor()
42+
if style != nil && style.TextColor() != nil {
43+
fg = style.TextColor()
44+
}
45+
bg := color.Color(color.Transparent)
46+
if style != nil && style.BackgroundColor() != nil {
47+
bg = style.BackgroundColor()
48+
}
49+
50+
if s, ok := style.(*TermTextGridStyle); ok && s != nil && s.BlinkEnabled {
51+
t.shouldBlink = true
52+
if t.blink {
53+
fg = bg
54+
}
55+
}
56+
57+
text := t.objects[pos*2+1].(*canvas.Text)
58+
text.TextSize = theme.TextSize()
59+
60+
newStr := string(str)
61+
if text.Text != newStr || text.Color != fg {
62+
text.Text = newStr
63+
text.Color = fg
64+
t.refresh(text)
65+
}
66+
67+
rect := t.objects[pos*2].(*canvas.Rectangle)
68+
if rect.FillColor != bg {
69+
rect.FillColor = bg
70+
t.refresh(rect)
71+
}
72+
}
73+
74+
func (t *termGridRenderer) addCellsIfRequired() {
75+
cellCount := t.cols * t.rows
76+
if len(t.objects) == cellCount*2 {
77+
return
78+
}
79+
for i := len(t.objects); i < cellCount*2; i += 2 {
80+
t.appendTextCell(' ')
81+
}
82+
}
83+
84+
func (t *termGridRenderer) refreshGrid() {
85+
line := 1
86+
x := 0
87+
// reset shouldBlink which can be set by setCellRune if a cell with BlinkEnabled is found
88+
t.shouldBlink = false
89+
90+
for rowIndex, row := range t.text.Rows {
91+
i := 0
92+
if t.text.ShowLineNumbers {
93+
lineStr := []rune(strconv.Itoa(line))
94+
pad := t.lineNumberWidth() - len(lineStr)
95+
for ; i < pad; i++ {
96+
t.setCellRune(' ', x, widget.TextGridStyleWhitespace) // padding space
97+
x++
98+
}
99+
for c := 0; c < len(lineStr); c++ {
100+
t.setCellRune(lineStr[c], x, widget.TextGridStyleDefault) // line numbers
101+
i++
102+
x++
103+
}
104+
105+
t.setCellRune('|', x, widget.TextGridStyleWhitespace) // last space
106+
i++
107+
x++
108+
}
109+
for _, r := range row.Cells {
110+
if i >= t.cols { // would be an overflow - bad
111+
continue
112+
}
113+
if t.text.ShowWhitespace && (r.Rune == ' ' || r.Rune == '\t') {
114+
sym := textAreaSpaceSymbol
115+
if r.Rune == '\t' {
116+
sym = textAreaTabSymbol
117+
}
118+
119+
if r.Style != nil && r.Style.BackgroundColor() != nil {
120+
whitespaceBG := &widget.CustomTextGridStyle{FGColor: widget.TextGridStyleWhitespace.TextColor(),
121+
BGColor: r.Style.BackgroundColor()}
122+
t.setCellRune(sym, x, whitespaceBG) // whitespace char
123+
} else {
124+
t.setCellRune(sym, x, widget.TextGridStyleWhitespace) // whitespace char
125+
}
126+
} else {
127+
t.setCellRune(r.Rune, x, r.Style) // regular char
128+
}
129+
i++
130+
x++
131+
}
132+
if t.text.ShowWhitespace && i < t.cols && rowIndex < len(t.text.Rows)-1 {
133+
t.setCellRune(textAreaNewLineSymbol, x, widget.TextGridStyleWhitespace) // newline
134+
i++
135+
x++
136+
}
137+
for ; i < t.cols; i++ {
138+
t.setCellRune(' ', x, widget.TextGridStyleDefault) // blanks
139+
x++
140+
}
141+
142+
line++
143+
}
144+
for ; x < len(t.objects)/2; x++ {
145+
t.setCellRune(' ', x, widget.TextGridStyleDefault) // trailing cells and blank lines
146+
}
147+
148+
switch {
149+
case t.shouldBlink && t.tickerCancel == nil:
150+
t.runBlink()
151+
case !t.shouldBlink && t.tickerCancel != nil:
152+
t.tickerCancel()
153+
t.tickerCancel = nil
154+
}
155+
}
156+
157+
func (t *termGridRenderer) runBlink() {
158+
if t.tickerCancel != nil {
159+
t.tickerCancel()
160+
t.tickerCancel = nil
161+
}
162+
var tickerContext context.Context
163+
tickerContext, t.tickerCancel = context.WithCancel(context.Background())
164+
ticker := time.NewTicker(blinkingInterval)
165+
blinking := false
166+
go func() {
167+
for {
168+
select {
169+
case <-tickerContext.Done():
170+
return
171+
case <-ticker.C:
172+
t.SetBlink(blinking)
173+
blinking = !blinking
174+
t.refreshGrid()
175+
}
176+
}
177+
}()
178+
}
179+
180+
func (t *termGridRenderer) lineNumberWidth() int {
181+
return len(strconv.Itoa(t.rows + 1))
182+
}
183+
184+
func (t *termGridRenderer) updateGridSize(size fyne.Size) {
185+
bufRows := len(t.text.Rows)
186+
bufCols := 0
187+
for _, row := range t.text.Rows {
188+
bufCols = int(math.Max(float64(bufCols), float64(len(row.Cells))))
189+
}
190+
sizeCols := math.Floor(float64(size.Width) / float64(t.cellSize.Width))
191+
sizeRows := math.Floor(float64(size.Height) / float64(t.cellSize.Height))
192+
193+
if t.text.ShowWhitespace {
194+
bufCols++
195+
}
196+
if t.text.ShowLineNumbers {
197+
bufCols += t.lineNumberWidth()
198+
}
199+
200+
t.cols = int(math.Max(sizeCols, float64(bufCols)))
201+
t.rows = int(math.Max(sizeRows, float64(bufRows)))
202+
t.addCellsIfRequired()
203+
}
204+
205+
func (t *termGridRenderer) Layout(size fyne.Size) {
206+
t.updateGridSize(size)
207+
208+
i := 0
209+
cellPos := fyne.NewPos(0, 0)
210+
for y := 0; y < t.rows; y++ {
211+
for x := 0; x < t.cols; x++ {
212+
t.objects[i*2+1].Move(cellPos)
213+
214+
t.objects[i*2].Resize(t.cellSize)
215+
t.objects[i*2].Move(cellPos)
216+
cellPos.X += t.cellSize.Width
217+
i++
218+
}
219+
220+
cellPos.X = 0
221+
cellPos.Y += t.cellSize.Height
222+
}
223+
}
224+
225+
func (t *termGridRenderer) MinSize() fyne.Size {
226+
longestRow := float32(0)
227+
for _, row := range t.text.Rows {
228+
longestRow = fyne.Max(longestRow, float32(len(row.Cells)))
229+
}
230+
return fyne.NewSize(t.cellSize.Width*longestRow,
231+
t.cellSize.Height*float32(len(t.text.Rows)))
232+
}
233+
234+
func (t *termGridRenderer) Refresh() {
235+
// we may be on a new canvas, so just update it to be sure
236+
if fyne.CurrentApp() != nil && fyne.CurrentApp().Driver() != nil {
237+
t.current = fyne.CurrentApp().Driver().CanvasForObject(t.text)
238+
}
239+
240+
// theme could change text size
241+
t.updateCellSize()
242+
243+
widget.TextGridStyleWhitespace = &widget.CustomTextGridStyle{FGColor: theme.DisabledColor()}
244+
t.updateGridSize(t.text.Size())
245+
t.refreshGrid()
246+
}
247+
248+
func (t *termGridRenderer) ApplyTheme() {
249+
}
250+
251+
func (t *termGridRenderer) Objects() []fyne.CanvasObject {
252+
return t.objects
253+
}
254+
255+
func (t *termGridRenderer) Destroy() {
256+
}
257+
258+
func (t *termGridRenderer) refresh(obj fyne.CanvasObject) {
259+
if t.current == nil {
260+
if fyne.CurrentApp() != nil && fyne.CurrentApp().Driver() != nil {
261+
// cache canvas for this widget, so we don't look it up many times for every cell/row refresh!
262+
t.current = fyne.CurrentApp().Driver().CanvasForObject(t.text)
263+
}
264+
265+
if t.current == nil {
266+
return // not yet set up perhaps?
267+
}
268+
}
269+
270+
t.current.Refresh(obj)
271+
}
272+
273+
func (t *termGridRenderer) updateCellSize() {
274+
size := fyne.MeasureText("M", theme.TextSize(), fyne.TextStyle{Monospace: true})
275+
276+
// round it for seamless background
277+
size.Width = float32(math.Round(float64((size.Width))))
278+
size.Height = float32(math.Round(float64((size.Height))))
279+
280+
t.cellSize = size
281+
}
282+
283+
func (t *termGridRenderer) SetBlink(b bool) {
284+
t.blink = b
285+
}

0 commit comments

Comments
 (0)