diff --git a/cmd/fyneterm/font/NotoSansMono/NotoSansMono-Bold.ttf b/cmd/fyneterm/font/NotoSansMono/NotoSansMono-Bold.ttf new file mode 100644 index 00000000..3daa161a Binary files /dev/null and b/cmd/fyneterm/font/NotoSansMono/NotoSansMono-Bold.ttf differ diff --git a/cmd/fyneterm/font/NotoSansMono/NotoSansMono-Regular.ttf b/cmd/fyneterm/font/NotoSansMono/NotoSansMono-Regular.ttf new file mode 100644 index 00000000..b7615d8e Binary files /dev/null and b/cmd/fyneterm/font/NotoSansMono/NotoSansMono-Regular.ttf differ diff --git a/cmd/fyneterm/font/NotoSansMono/notosansmono.go b/cmd/fyneterm/font/NotoSansMono/notosansmono.go new file mode 100644 index 00000000..4276d63a --- /dev/null +++ b/cmd/fyneterm/font/NotoSansMono/notosansmono.go @@ -0,0 +1,26 @@ +package notosansmono + +import ( + // go embed. + _ "embed" + + "fyne.io/fyne/v2" +) + +//go:embed NotoSansMono-Regular.ttf +var regular []byte + +// Regular is the regular font resource. +var Regular = &fyne.StaticResource{ + StaticName: "NotoSansMono-Regular.ttf", + StaticContent: regular, +} + +//go:embed NotoSansMono-Bold.ttf +var bold []byte + +// Bold is the bold font resource. +var Bold = &fyne.StaticResource{ + StaticName: "NotoSansMono-Bold.ttf", + StaticContent: bold, +} diff --git a/cmd/fyneterm/theme.go b/cmd/fyneterm/theme.go index 00fec2aa..e18b8d3a 100644 --- a/cmd/fyneterm/theme.go +++ b/cmd/fyneterm/theme.go @@ -5,6 +5,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" + notosansmono "github.com/fyne-io/terminal/cmd/fyneterm/font/NotoSansMono" ) type termTheme struct { @@ -41,3 +42,12 @@ func (t *termTheme) Size(n fyne.ThemeSizeName) float32 { return t.Theme.Size(n) } + +func (t *termTheme) Font(style fyne.TextStyle) fyne.Resource { + switch { + case style.Bold: + return notosansmono.Bold + default: + return notosansmono.Regular + } +} diff --git a/color.go b/color.go index 9b0ddbd2..7626d356 100644 --- a/color.go +++ b/color.go @@ -47,6 +47,7 @@ func (t *Terminal) handleColorEscape(message string) { t.currentFG = nil t.bold = false t.blinking = false + t.underlined = false return } modes := strings.Split(message, ";") @@ -82,11 +83,15 @@ func (t *Terminal) handleColorMode(modeStr string) { t.currentBG, t.currentFG = nil, nil t.bold = false t.blinking = false + t.underlined = false case 1: t.bold = true - case 4, 24: //italic + case 4: + t.underlined = true case 5: t.blinking = true + case 24: + t.underlined = false case 7: // reverse bg, fg := t.currentBG, t.currentFG if fg == nil { diff --git a/color_test.go b/color_test.go index dde7712a..ee16c28b 100644 --- a/color_test.go +++ b/color_test.go @@ -45,13 +45,23 @@ func testColor(t *testing.T, tests map[string]struct { func TestHandleOutput_Text(t *testing.T) { tests := map[string]struct { - inputSeq string - expectBold bool + inputSeq string + expectBold bool + expectUnderline bool }{ "bold": { inputSeq: esc("[1m"), expectBold: true, }, + "underline": { + inputSeq: esc("[4m"), + expectUnderline: true, + }, + "bold and underline": { + inputSeq: esc("[1m") + esc("[4m"), + expectBold: true, + expectUnderline: true, + }, } // Iterate through the test cases @@ -60,6 +70,11 @@ func TestHandleOutput_Text(t *testing.T) { terminal := New() terminal.handleOutput([]byte(test.inputSeq)) + // Verify the actual results match the expected results + if terminal.underlined != test.expectUnderline { + t.Errorf("Bold flag mismatch. Got %v, expected %v", terminal.underlined, test.expectUnderline) + } + if terminal.bold != test.expectBold { t.Errorf("Bold flag mismatch. Got %v, expected %v", terminal.bold, test.expectBold) } @@ -921,10 +936,10 @@ func TestHandleOutput_BufferCutoff(t *testing.T) { tg.Rows = []widget.TextGridRow{ { Cells: []widget.TextGridCell{ - {Rune: '4', Style: &widget.CustomTextGridStyle{FGColor: c1, BGColor: nil}}, - {Rune: '0', Style: &widget.CustomTextGridStyle{FGColor: c1, BGColor: nil}}, - {Rune: '4', Style: &widget.CustomTextGridStyle{FGColor: c2, BGColor: nil}}, - {Rune: '1', Style: &widget.CustomTextGridStyle{FGColor: c2, BGColor: nil}}, + {Rune: '4', Style: widget2.NewTermTextGridStyle(c1, nil, 0x55, false, false, false)}, + {Rune: '0', Style: widget2.NewTermTextGridStyle(c1, nil, 0x55, false, false, false)}, + {Rune: '4', Style: widget2.NewTermTextGridStyle(c2, nil, 0x55, false, false, false)}, + {Rune: '1', Style: widget2.NewTermTextGridStyle(c2, nil, 0x55, false, false, false)}, }, }, } diff --git a/internal/widget/renderer.go b/internal/widget/renderer.go new file mode 100644 index 00000000..804c7186 --- /dev/null +++ b/internal/widget/renderer.go @@ -0,0 +1,328 @@ +package widget + +import ( + "context" + "image/color" + "math" + "strconv" + "time" + + "fyne.io/fyne/v2/widget" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/theme" +) + +type termGridRenderer struct { + text *TermGrid + + cols, rows int + + cellSize fyne.Size + objects []fyne.CanvasObject + current fyne.Canvas + blink bool + shouldBlink bool + tickerCancel context.CancelFunc +} + +func (t *termGridRenderer) appendTextCell(str rune) { + th := t.text.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + text := canvas.NewText(string(str), th.Color(theme.ColorNameForeground, v)) + text.TextStyle.Monospace = true + + bg := canvas.NewRectangle(color.Transparent) + + ul := canvas.NewLine(color.Transparent) + + t.objects = append(t.objects, bg, text, ul) +} + +func (t *termGridRenderer) setCellRune(str rune, pos int, style widget.TextGridStyle) { + if str == 0 { + str = ' ' + } + rect := t.objects[pos*3].(*canvas.Rectangle) + text := t.objects[pos*3+1].(*canvas.Text) + underline := t.objects[pos*3+2].(*canvas.Line) + + th := t.text.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + fg := th.Color(theme.ColorNameForeground, v) + bg := color.Color(color.Transparent) + text.TextSize = th.Size(theme.SizeNameText) + textStyle := fyne.TextStyle{} + var underlineStrokeWidth float32 = 1 + var underlineStrokeColor color.Color = color.Transparent + + if style != nil && style.TextColor() != nil { + fg = style.TextColor() + } + + if style != nil { + if style.BackgroundColor() != nil { + bg = style.BackgroundColor() + } + if style.Style().Bold { + underlineStrokeWidth = 2 + textStyle = fyne.TextStyle{ + Bold: true, + } + } + if style.Style().Underline { + underlineStrokeColor = fg + } + } + + if s, ok := style.(*TermTextGridStyle); ok && s != nil && s.BlinkEnabled { + t.shouldBlink = true + if t.blink { + fg = bg + underlineStrokeColor = bg + } + } + + newStr := string(str) + if text.Text != newStr || text.Color != fg || textStyle != text.TextStyle { + text.Text = newStr + text.Color = fg + text.TextStyle = textStyle + t.refresh(text) + } + + if underlineStrokeWidth != underline.StrokeWidth || underlineStrokeColor != underline.StrokeColor { + underline.StrokeWidth, underline.StrokeColor = underlineStrokeWidth, underlineStrokeColor + t.refresh(underline) + } + + if rect.FillColor != bg { + rect.FillColor = bg + t.refresh(rect) + } +} + +func (t *termGridRenderer) addCellsIfRequired() { + cellCount := t.cols * t.rows + if len(t.objects) == cellCount*3 { + return + } + for i := len(t.objects); i < cellCount*3; i += 3 { + t.appendTextCell(' ') + } +} + +func (t *termGridRenderer) refreshGrid() { + line := 1 + x := 0 + // reset shouldBlink which can be set by setCellRune if a cell with BlinkEnabled is found + t.shouldBlink = false + + for rowIndex, row := range t.text.Rows { + i := 0 + if t.text.ShowLineNumbers { + lineStr := []rune(strconv.Itoa(line)) + pad := t.lineNumberWidth() - len(lineStr) + for ; i < pad; i++ { + t.setCellRune(' ', x, widget.TextGridStyleWhitespace) // padding space + x++ + } + for c := 0; c < len(lineStr); c++ { + t.setCellRune(lineStr[c], x, widget.TextGridStyleDefault) // line numbers + i++ + x++ + } + + t.setCellRune('|', x, widget.TextGridStyleWhitespace) // last space + i++ + x++ + } + for _, r := range row.Cells { + if i >= t.cols { // would be an overflow - bad + continue + } + if t.text.ShowWhitespace && (r.Rune == ' ' || r.Rune == '\t') { + sym := textAreaSpaceSymbol + if r.Rune == '\t' { + sym = textAreaTabSymbol + } + + if r.Style != nil && r.Style.BackgroundColor() != nil { + whitespaceBG := &widget.CustomTextGridStyle{FGColor: widget.TextGridStyleWhitespace.TextColor(), + BGColor: r.Style.BackgroundColor()} + t.setCellRune(sym, x, whitespaceBG) // whitespace char + } else { + t.setCellRune(sym, x, widget.TextGridStyleWhitespace) // whitespace char + } + } else { + t.setCellRune(r.Rune, x, r.Style) // regular char + } + i++ + x++ + } + if t.text.ShowWhitespace && i < t.cols && rowIndex < len(t.text.Rows)-1 { + t.setCellRune(textAreaNewLineSymbol, x, widget.TextGridStyleWhitespace) // newline + i++ + x++ + } + for ; i < t.cols; i++ { + t.setCellRune(' ', x, widget.TextGridStyleDefault) // blanks + x++ + } + + line++ + } + for ; x < len(t.objects)/3; x++ { + t.setCellRune(' ', x, widget.TextGridStyleDefault) // trailing cells and blank lines + } + + switch { + case t.shouldBlink && t.tickerCancel == nil: + t.runBlink() + case !t.shouldBlink && t.tickerCancel != nil: + t.tickerCancel() + t.tickerCancel = nil + } +} + +func (t *termGridRenderer) runBlink() { + if t.tickerCancel != nil { + t.tickerCancel() + t.tickerCancel = nil + } + var tickerContext context.Context + tickerContext, t.tickerCancel = context.WithCancel(context.Background()) + ticker := time.NewTicker(blinkingInterval) + blinking := false + go func() { + for { + select { + case <-tickerContext.Done(): + return + case <-ticker.C: + t.SetBlink(blinking) + blinking = !blinking + t.refreshGrid() + } + } + }() +} + +func (t *termGridRenderer) lineNumberWidth() int { + return len(strconv.Itoa(t.rows + 1)) +} + +func (t *termGridRenderer) updateGridSize(size fyne.Size) { + bufRows := len(t.text.Rows) + bufCols := 0 + for _, row := range t.text.Rows { + bufCols = int(math.Max(float64(bufCols), float64(len(row.Cells)))) + } + sizeCols := math.Floor(float64(size.Width) / float64(t.cellSize.Width)) + sizeRows := math.Floor(float64(size.Height) / float64(t.cellSize.Height)) + + if t.text.ShowWhitespace { + bufCols++ + } + if t.text.ShowLineNumbers { + bufCols += t.lineNumberWidth() + } + + t.cols = int(math.Max(sizeCols, float64(bufCols))) + t.rows = int(math.Max(sizeRows, float64(bufRows))) + t.addCellsIfRequired() +} + +func (t *termGridRenderer) Layout(size fyne.Size) { + t.updateGridSize(size) + + i := 0 + cellPos := fyne.NewPos(0, 0) + for y := 0; y < t.rows; y++ { + for x := 0; x < t.cols; x++ { + // rect + t.objects[i*3].Resize(t.cellSize) + t.objects[i*3].Move(cellPos) + + // text + t.objects[i*3+1].Move(cellPos) + + // underline + t.objects[i*3+2].Move(cellPos.Add(fyne.Position{X: 0, Y: t.cellSize.Height})) + t.objects[i*3+2].Resize(fyne.Size{Width: t.cellSize.Width}) + + cellPos.X += t.cellSize.Width + i++ + } + + cellPos.X = 0 + cellPos.Y += t.cellSize.Height + } +} + +func (t *termGridRenderer) MinSize() fyne.Size { + longestRow := float32(0) + for _, row := range t.text.Rows { + longestRow = fyne.Max(longestRow, float32(len(row.Cells))) + } + return fyne.NewSize(t.cellSize.Width*longestRow, + t.cellSize.Height*float32(len(t.text.Rows))) +} + +func (t *termGridRenderer) Refresh() { + // we may be on a new canvas, so just update it to be sure + if fyne.CurrentApp() != nil && fyne.CurrentApp().Driver() != nil { + t.current = fyne.CurrentApp().Driver().CanvasForObject(t.text) + } + + // theme could change text size + t.updateCellSize() + + th := t.text.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + widget.TextGridStyleWhitespace = &widget.CustomTextGridStyle{FGColor: th.Color(theme.ColorNameDisabled, v)} + t.updateGridSize(t.text.Size()) + t.refreshGrid() +} + +func (t *termGridRenderer) ApplyTheme() { +} + +func (t *termGridRenderer) Objects() []fyne.CanvasObject { + return t.objects +} + +func (t *termGridRenderer) Destroy() { +} + +func (t *termGridRenderer) refresh(obj fyne.CanvasObject) { + if t.current == nil { + if fyne.CurrentApp() != nil && fyne.CurrentApp().Driver() != nil { + // cache canvas for this widget, so we don't look it up many times for every cell/row refresh! + t.current = fyne.CurrentApp().Driver().CanvasForObject(t.text) + } + + if t.current == nil { + return // not yet set up perhaps? + } + } + + t.current.Refresh(obj) +} + +func (t *termGridRenderer) updateCellSize() { + th := t.text.Theme() + size := fyne.MeasureText("M", th.Size(theme.SizeNameText), fyne.TextStyle{Monospace: true}) + + // round it for seamless background + size.Width = float32(math.Round(float64(size.Width))) + size.Height = float32(math.Round(float64(size.Height))) + + t.cellSize = size +} + +func (t *termGridRenderer) SetBlink(b bool) { + t.blink = b +} diff --git a/internal/widget/termgrid.go b/internal/widget/termgrid.go index 2fb460d9..27ef9c0b 100644 --- a/internal/widget/termgrid.go +++ b/internal/widget/termgrid.go @@ -1,16 +1,11 @@ package widget import ( - "context" - "image/color" - "math" - "strconv" "time" "fyne.io/fyne/v2/widget" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" ) @@ -45,274 +40,3 @@ func NewTermGrid() *TermGrid { grid.ExtendBaseWidget(grid) return grid } - -type termGridRenderer struct { - text *TermGrid - - cols, rows int - - cellSize fyne.Size - objects []fyne.CanvasObject - current fyne.Canvas - blink bool - shouldBlink bool - tickerCancel context.CancelFunc -} - -func (t *termGridRenderer) appendTextCell(str rune) { - text := canvas.NewText(string(str), theme.Color(theme.ColorNameForeground)) - text.TextStyle.Monospace = true - - bg := canvas.NewRectangle(color.Transparent) - t.objects = append(t.objects, bg, text) -} - -func (t *termGridRenderer) setCellRune(str rune, pos int, style widget.TextGridStyle) { - if str == 0 { - str = ' ' - } - fg := theme.Color(theme.ColorNameForeground) - if style != nil && style.TextColor() != nil { - fg = style.TextColor() - } - bg := color.Color(color.Transparent) - if style != nil && style.BackgroundColor() != nil { - bg = style.BackgroundColor() - } - - if s, ok := style.(*TermTextGridStyle); ok && s != nil && s.BlinkEnabled { - t.shouldBlink = true - if t.blink { - fg = bg - } - } - - text := t.objects[pos*2+1].(*canvas.Text) - text.TextSize = theme.TextSize() - - newStr := string(str) - if text.Text != newStr || text.Color != fg { - text.Text = newStr - text.Color = fg - t.refresh(text) - } - - rect := t.objects[pos*2].(*canvas.Rectangle) - if rect.FillColor != bg { - rect.FillColor = bg - t.refresh(rect) - } -} - -func (t *termGridRenderer) addCellsIfRequired() { - cellCount := t.cols * t.rows - if len(t.objects) == cellCount*2 { - return - } - for i := len(t.objects); i < cellCount*2; i += 2 { - t.appendTextCell(' ') - } -} - -func (t *termGridRenderer) refreshGrid() { - line := 1 - x := 0 - // reset shouldBlink which can be set by setCellRune if a cell with BlinkEnabled is found - t.shouldBlink = false - - for rowIndex, row := range t.text.Rows { - i := 0 - if t.text.ShowLineNumbers { - lineStr := []rune(strconv.Itoa(line)) - pad := t.lineNumberWidth() - len(lineStr) - for ; i < pad; i++ { - t.setCellRune(' ', x, widget.TextGridStyleWhitespace) // padding space - x++ - } - for c := 0; c < len(lineStr); c++ { - t.setCellRune(lineStr[c], x, widget.TextGridStyleDefault) // line numbers - i++ - x++ - } - - t.setCellRune('|', x, widget.TextGridStyleWhitespace) // last space - i++ - x++ - } - for _, r := range row.Cells { - if i >= t.cols { // would be an overflow - bad - continue - } - if t.text.ShowWhitespace && (r.Rune == ' ' || r.Rune == '\t') { - sym := textAreaSpaceSymbol - if r.Rune == '\t' { - sym = textAreaTabSymbol - } - - if r.Style != nil && r.Style.BackgroundColor() != nil { - whitespaceBG := &widget.CustomTextGridStyle{FGColor: widget.TextGridStyleWhitespace.TextColor(), - BGColor: r.Style.BackgroundColor()} - t.setCellRune(sym, x, whitespaceBG) // whitespace char - } else { - t.setCellRune(sym, x, widget.TextGridStyleWhitespace) // whitespace char - } - } else { - t.setCellRune(r.Rune, x, r.Style) // regular char - } - i++ - x++ - } - if t.text.ShowWhitespace && i < t.cols && rowIndex < len(t.text.Rows)-1 { - t.setCellRune(textAreaNewLineSymbol, x, widget.TextGridStyleWhitespace) // newline - i++ - x++ - } - for ; i < t.cols; i++ { - t.setCellRune(' ', x, widget.TextGridStyleDefault) // blanks - x++ - } - - line++ - } - for ; x < len(t.objects)/2; x++ { - t.setCellRune(' ', x, widget.TextGridStyleDefault) // trailing cells and blank lines - } - - switch { - case t.shouldBlink && t.tickerCancel == nil: - t.runBlink() - case !t.shouldBlink && t.tickerCancel != nil: - t.tickerCancel() - t.tickerCancel = nil - } -} - -func (t *termGridRenderer) runBlink() { - if t.tickerCancel != nil { - t.tickerCancel() - t.tickerCancel = nil - } - var tickerContext context.Context - tickerContext, t.tickerCancel = context.WithCancel(context.Background()) - ticker := time.NewTicker(blinkingInterval) - blinking := false - go func() { - for { - select { - case <-tickerContext.Done(): - return - case <-ticker.C: - t.SetBlink(blinking) - blinking = !blinking - t.refreshGrid() - } - } - }() -} - -func (t *termGridRenderer) lineNumberWidth() int { - return len(strconv.Itoa(t.rows + 1)) -} - -func (t *termGridRenderer) updateGridSize(size fyne.Size) { - bufRows := len(t.text.Rows) - bufCols := 0 - for _, row := range t.text.Rows { - bufCols = int(math.Max(float64(bufCols), float64(len(row.Cells)))) - } - sizeCols := math.Floor(float64(size.Width) / float64(t.cellSize.Width)) - sizeRows := math.Floor(float64(size.Height) / float64(t.cellSize.Height)) - - if t.text.ShowWhitespace { - bufCols++ - } - if t.text.ShowLineNumbers { - bufCols += t.lineNumberWidth() - } - - t.cols = int(math.Max(sizeCols, float64(bufCols))) - t.rows = int(math.Max(sizeRows, float64(bufRows))) - t.addCellsIfRequired() -} - -func (t *termGridRenderer) Layout(size fyne.Size) { - t.updateGridSize(size) - - i := 0 - cellPos := fyne.NewPos(0, 0) - for y := 0; y < t.rows; y++ { - for x := 0; x < t.cols; x++ { - t.objects[i*2+1].Move(cellPos) - - t.objects[i*2].Resize(t.cellSize) - t.objects[i*2].Move(cellPos) - cellPos.X += t.cellSize.Width - i++ - } - - cellPos.X = 0 - cellPos.Y += t.cellSize.Height - } -} - -func (t *termGridRenderer) MinSize() fyne.Size { - longestRow := float32(0) - for _, row := range t.text.Rows { - longestRow = fyne.Max(longestRow, float32(len(row.Cells))) - } - return fyne.NewSize(t.cellSize.Width*longestRow, - t.cellSize.Height*float32(len(t.text.Rows))) -} - -func (t *termGridRenderer) Refresh() { - // we may be on a new canvas, so just update it to be sure - if fyne.CurrentApp() != nil && fyne.CurrentApp().Driver() != nil { - t.current = fyne.CurrentApp().Driver().CanvasForObject(t.text) - } - - // theme could change text size - t.updateCellSize() - - widget.TextGridStyleWhitespace = &widget.CustomTextGridStyle{FGColor: theme.Color(theme.ColorNameDisabled)} - t.updateGridSize(t.text.Size()) - t.refreshGrid() -} - -func (t *termGridRenderer) ApplyTheme() { -} - -func (t *termGridRenderer) Objects() []fyne.CanvasObject { - return t.objects -} - -func (t *termGridRenderer) Destroy() { -} - -func (t *termGridRenderer) refresh(obj fyne.CanvasObject) { - if t.current == nil { - if fyne.CurrentApp() != nil && fyne.CurrentApp().Driver() != nil { - // cache canvas for this widget, so we don't look it up many times for every cell/row refresh! - t.current = fyne.CurrentApp().Driver().CanvasForObject(t.text) - } - - if t.current == nil { - return // not yet set up perhaps? - } - } - - t.current.Refresh(obj) -} - -func (t *termGridRenderer) updateCellSize() { - size := fyne.MeasureText("M", theme.TextSize(), fyne.TextStyle{Monospace: true}) - - // round it for seamless background - size.Width = float32(math.Round(float64((size.Width)))) - size.Height = float32(math.Round(float64((size.Height)))) - - t.cellSize = size -} - -func (t *termGridRenderer) SetBlink(b bool) { - t.blink = b -} diff --git a/internal/widget/termgridhelper.go b/internal/widget/termgridhelper.go index 33150173..e095065f 100644 --- a/internal/widget/termgridhelper.go +++ b/internal/widget/termgridhelper.go @@ -15,9 +15,9 @@ func HighlightRange(t *TermGrid, blockMode bool, startRow, startCol, endRow, end // Check if already highlighted if h, ok := cell.Style.(*TermTextGridStyle); !ok { if cell.Style != nil { - cell.Style = NewTermTextGridStyle(cell.Style.TextColor(), cell.Style.BackgroundColor(), bitmask, false) + cell.Style = NewTermTextGridStyle(cell.Style.TextColor(), cell.Style.BackgroundColor(), bitmask, false, false, false) } else { - cell.Style = NewTermTextGridStyle(nil, nil, bitmask, false) + cell.Style = NewTermTextGridStyle(nil, nil, bitmask, false, false, false) } cell.Style.(*TermTextGridStyle).Highlighted = true @@ -214,7 +214,7 @@ type HighlightOption func(h *TermTextGridStyle) // Returns: // // A pointer to a TermTextGridStyle initialized with the provided colors and inversion settings. -func NewTermTextGridStyle(fg, bg color.Color, bitmask byte, blinkEnabled bool) widget.TextGridStyle { +func NewTermTextGridStyle(fg, bg color.Color, bitmask byte, blinkEnabled, bold, underlined bool) widget.TextGridStyle { // calculate the inverted colors var invertedFg, invertedBg color.Color if fg == nil { @@ -235,6 +235,10 @@ func NewTermTextGridStyle(fg, bg color.Color, bitmask byte, blinkEnabled bool) w InvertedBackgroundColor: invertedBg, Highlighted: false, BlinkEnabled: blinkEnabled, + TextStyle: fyne.TextStyle{ + Bold: bold, + Underline: underlined, + }, } } diff --git a/output.go b/output.go index bef82228..b7212950 100644 --- a/output.go +++ b/output.go @@ -248,8 +248,7 @@ func (t *Terminal) handleOutputChar(r rune) { t.content.Rows = append(t.content.Rows, widget.TextGridRow{}) } - var cellStyle widget.TextGridStyle - cellStyle = &widget.CustomTextGridStyle{FGColor: t.currentFG, BGColor: t.currentBG} + cellStyle := widget2.NewTermTextGridStyle(t.currentFG, t.currentBG, t.highlightBitMask, t.blinking, t.bold, t.underlined) for len(t.content.Rows[t.cursorRow].Cells)-1 < t.cursorCol { newCell := widget.TextGridCell{ Rune: ' ', @@ -257,9 +256,6 @@ func (t *Terminal) handleOutputChar(r rune) { } t.content.Rows[t.cursorRow].Cells = append(t.content.Rows[t.cursorRow].Cells, newCell) } - if t.blinking { - cellStyle = widget2.NewTermTextGridStyle(t.currentFG, t.currentBG, t.highlightBitMask, t.blinking) - } t.content.SetCell(t.cursorRow, t.cursorCol, widget.TextGridCell{Rune: r, Style: cellStyle}) t.cursorCol++ } diff --git a/term.go b/term.go index cae57614..20a47e40 100644 --- a/term.go +++ b/term.go @@ -82,6 +82,7 @@ type Terminal struct { bracketedPasteMode bool state *parseState blinking bool + underlined bool printData []byte printer Printer cmd *exec.Cmd