Skip to content

Commit 7ca9e07

Browse files
authored
Merge pull request #3 from codelaboratoryltd/blink
Add blinking
2 parents 26f201f + b5864dd commit 7ca9e07

File tree

4 files changed

+87
-14
lines changed

4 files changed

+87
-14
lines changed

color.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func (t *Terminal) handleColorEscape(message string) {
4646
t.currentBG = nil
4747
t.currentFG = nil
4848
t.bold = false
49+
t.blinking = false
4950
return
5051
}
5152

@@ -81,9 +82,12 @@ func (t *Terminal) handleColorMode(modeStr string) {
8182
case 0:
8283
t.currentBG, t.currentFG = nil, nil
8384
t.bold = false
85+
t.blinking = false
8486
case 1:
8587
t.bold = true
8688
case 4, 24: //italic
89+
case 5:
90+
t.blinking = true
8791
case 7: // reverse
8892
bg := t.currentBG
8993
if t.currentFG == nil {

internal/widget/textgrid.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ func HighlightRange(t *widget.TextGrid, blockMode bool, startRow, startCol, endR
1414
// Check if already highlighted
1515
if h, ok := cell.Style.(*TermTextGridStyle); !ok {
1616
if cell.Style != nil {
17-
cell.Style = NewTermTextGridStyle(cell.Style.TextColor(), cell.Style.BackgroundColor(), bitmask)
17+
cell.Style = NewTermTextGridStyle(cell.Style.TextColor(), cell.Style.BackgroundColor(), bitmask, false)
1818
} else {
19-
cell.Style = NewTermTextGridStyle(nil, nil, bitmask)
19+
cell.Style = NewTermTextGridStyle(nil, nil, bitmask, false)
2020
}
2121
cell.Style.(*TermTextGridStyle).Highlighted = true
2222

@@ -80,14 +80,14 @@ func GetTextRange(t *widget.TextGrid, blockMode bool, startRow, startCol, endRow
8080
// - eachRow (func(row *widget.TextGridRow)): A function that takes a pointer to a TextGridRow and is applied to each row in the specified range. Pass `nil` if you don't want to apply a row function.
8181
//
8282
// Note:
83-
// - If startRow or endRow are out of bounds (negative or greater/equal to the number of rows in the TermGrid), they will be adjusted to valid values.
83+
// - If startRow or endRow are out of bounds (negative or greater/equal to the number of rows in the TextGrid), they will be adjusted to valid values.
8484
// - If startRow and endRow are the same, the iteration will be limited to the specified column range within that row.
8585
// - When blockMode is true, it iterates through rows from startRow to endRow, applying the cell function for each cell in the specified column range.
8686
// - When blockMode is false, it iterates through individual cells row by row, applying the cell function for each cell and optionally applying the row function for each row.
8787
//
8888
// Example Usage:
89-
// termGrid.forRange(true, 0, 1, 2, 3, cellFunc, rowFunc) // Iterate in block mode, applying cellFunc to cells in columns 1 to 3 and rowFunc to rows 0 to 2.
90-
// termGrid.forRange(false, 1, 0, 3, 2, cellFunc, rowFunc) // Iterate cell by cell, applying cellFunc to all cells and rowFunc to rows 1 and 2.
89+
// forRange(termGrid, true, 0, 1, 2, 3, cellFunc, rowFunc) // Iterate in block mode, applying cellFunc to cells in columns 1 to 3 and rowFunc to rows 0 to 2.
90+
// forRange(termGrid, false, 1, 0, 3, 2, cellFunc, rowFunc) // Iterate cell by cell, applying cellFunc to all cells and rowFunc to rows 1 and 2.
9191
func forRange(t *widget.TextGrid, blockMode bool, startRow, startCol, endRow, endCol int, eachCell func(cell *widget.TextGridCell), eachRow func(row *widget.TextGridRow)) {
9292
if startRow >= len(t.Rows) || endRow < 0 {
9393
return
@@ -171,13 +171,21 @@ type TermTextGridStyle struct {
171171
InvertedTextColor color.Color
172172
InvertedBackgroundColor color.Color
173173
Highlighted bool
174+
BlinkEnabled bool
175+
Blink bool
174176
}
175177

176178
// TextColor returns the color of the text, depending on whether it is highlighted.
177179
func (h *TermTextGridStyle) TextColor() color.Color {
178180
if h.Highlighted {
181+
if h.BlinkEnabled && h.Blink {
182+
return h.InvertedBackgroundColor
183+
}
179184
return h.InvertedTextColor
180185
}
186+
if h.BlinkEnabled && h.Blink {
187+
return h.OriginalBackgroundColor
188+
}
181189
return h.OriginalTextColor
182190
}
183191

@@ -201,11 +209,12 @@ type HighlightOption func(h *TermTextGridStyle)
201209
// - fg: The foreground color.
202210
// - bg: The background color.
203211
// - bitmask: The bitmask to control color inversion.
212+
// - blinkEnabled: Should this cell blink when told to.
204213
//
205214
// Returns:
206215
//
207216
// A pointer to a TermTextGridStyle initialized with the provided colors and inversion settings.
208-
func NewTermTextGridStyle(fg, bg color.Color, bitmask byte) widget.TextGridStyle {
217+
func NewTermTextGridStyle(fg, bg color.Color, bitmask byte, blinkEnabled bool) widget.TextGridStyle {
209218
// calculate the inverted colors
210219
var invertedFg, invertedBg color.Color
211220
if fg == nil {
@@ -225,6 +234,8 @@ func NewTermTextGridStyle(fg, bg color.Color, bitmask byte) widget.TextGridStyle
225234
InvertedTextColor: invertedFg,
226235
InvertedBackgroundColor: invertedBg,
227236
Highlighted: false,
237+
BlinkEnabled: blinkEnabled,
238+
Blink: false,
228239
}
229240
}
230241

output.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"time"
55

66
"fyne.io/fyne/v2/widget"
7+
widget2 "github.com/fyne-io/terminal/internal/widget"
78
)
89

910
const (
@@ -13,6 +14,8 @@ const (
1314

1415
noEscape = 5000
1516
tabWidth = 8
17+
18+
blinkingInterval = 500 * time.Millisecond
1619
)
1720

1821
var charSetMap = map[charSet]func(rune) rune{
@@ -187,15 +190,18 @@ func (t *Terminal) handleOutputChar(r rune) {
187190
t.content.Rows = append(t.content.Rows, widget.TextGridRow{})
188191
}
189192

190-
cellStyle := &widget.CustomTextGridStyle{FGColor: t.currentFG, BGColor: t.currentBG}
193+
var cellStyle widget.TextGridStyle
194+
cellStyle = &widget.CustomTextGridStyle{FGColor: t.currentFG, BGColor: t.currentBG}
191195
for len(t.content.Rows[t.cursorRow].Cells)-1 < t.cursorCol {
192196
newCell := widget.TextGridCell{
193197
Rune: ' ',
194198
Style: cellStyle,
195199
}
196200
t.content.Rows[t.cursorRow].Cells = append(t.content.Rows[t.cursorRow].Cells, newCell)
197201
}
198-
202+
if t.blinking {
203+
cellStyle = widget2.NewTermTextGridStyle(t.currentFG, t.currentBG, t.highlightBitMask, t.blinking)
204+
}
199205
t.content.SetCell(t.cursorRow, t.cursorCol, widget.TextGridCell{Rune: r, Style: cellStyle})
200206
t.cursorCol++
201207
}

term.go

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import (
1717
widget2 "github.com/fyne-io/terminal/internal/widget"
1818
)
1919

20+
const (
21+
bufLen = 4069
22+
)
23+
2024
// Config is the state of a terminal, updated upon certain actions or commands.
2125
// Use Terminal.OnConfigure hook to register for changes.
2226
type Config struct {
@@ -74,6 +78,7 @@ type Terminal struct {
7478
}
7579
newLineMode bool // new line mode or line feed mode
7680
bracketedPasteMode bool
81+
blinking bool
7782
}
7883

7984
// Cursor is used for displaying a specific cursor.
@@ -264,26 +269,73 @@ func (t *Terminal) guessCellSize() fyne.Size {
264269
return fyne.NewSize(float32(math.Round(float64(min.Width))), float32(math.Round(float64(min.Height))))
265270
}
266271

272+
// run starts the main loop for handling terminal output, blinking, and refreshing.
273+
// It reads terminal output asynchronously, processes it, and toggles blinking every
274+
// blinkingInterval duration.
275+
// The function returns when the terminal is closed.
267276
func (t *Terminal) run() {
268-
bufLen := 4069
277+
ch := make(chan []byte)
278+
ticker := time.NewTicker(blinkingInterval)
279+
blinking := false
280+
go t.readOutAsync(ch)
281+
282+
for {
283+
select {
284+
case b, ok := <-ch:
285+
if !ok {
286+
// we've been closed
287+
return
288+
}
289+
t.handleOutput(b)
290+
if len(b) < bufLen {
291+
t.Refresh()
292+
}
293+
case <-ticker.C:
294+
blinking = !blinking
295+
t.runBlink(blinking)
296+
}
297+
}
298+
}
299+
300+
// runBlink manages the blinking effect for cells in the terminal content.
301+
// It toggles the blinking state for blinking cells and refreshes the content as needed.
302+
func (t *Terminal) runBlink(blinking bool) {
303+
for rowNo, r := range t.content.Rows {
304+
for colNo, c := range r.Cells {
305+
s, ok := c.Style.(*widget2.TermTextGridStyle)
306+
if ok {
307+
s.Blink = blinking
308+
}
309+
310+
_, _ = rowNo, colNo
311+
}
312+
}
313+
314+
// redraw the cells we just flipped
315+
t.content.Refresh()
316+
}
317+
318+
// readOutAsync reads terminal output asynchronously and sends it to the provided channel.
319+
// It handles when the terminal is closed or encounters an error. The chanel is closed on returning.
320+
func (t *Terminal) readOutAsync(ch chan []byte) {
269321
buf := make([]byte, bufLen)
270322
for {
271323
num, err := t.out.Read(buf)
272324
if err != nil {
273325
// this is the pre-go 1.13 way to check for the read failing (terminal closed)
274326
if err.Error() == "EOF" {
327+
close(ch)
275328
break // term exit on macOS
276329
} else if err, ok := err.(*os.PathError); ok && err.Err.Error() == "input/output error" {
330+
close(ch)
277331
break // broken pipe, terminal exit
278332
}
279333

280334
fyne.LogError("pty read error", err)
281335
}
282-
283-
t.handleOutput(buf[:num])
284-
if num < bufLen {
285-
t.Refresh()
286-
}
336+
cp := make([]byte, num)
337+
copy(cp, buf[:num])
338+
ch <- cp
287339
}
288340
}
289341

0 commit comments

Comments
 (0)