Skip to content

Commit

Permalink
Add support for backgroun color in textgrid cell.
Browse files Browse the repository at this point in the history
This updates the simple color API to a fuller style API,
it should now be easier to extend the style definition in the future.
We can also now set semantic styles and not worry about colour definition
  • Loading branch information
andydotxyz committed Mar 13, 2020
1 parent bc298b8 commit cb7580f
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 31 deletions.
6 changes: 6 additions & 0 deletions cmd/fyne_demo/screens/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package screens

import (
"fmt"
"image/color"
"time"

"fyne.io/fyne"
Expand All @@ -14,7 +15,12 @@ import (
func makeButtonTab() fyne.Widget {
disabled := widget.NewButton("Disabled", func() {})
disabled.Disable()

grid := widget.NewTextGridFromString("TextGrid\n Content")
grid.SetStyleRange(0, 0, 0, 3,
&widget.CustomTextGridStyle{FGColor: color.RGBA{R: 0, G: 0, B: 128, A: 255}})
grid.SetStyleRange(0, 4, 0, 7,
&widget.CustomTextGridStyle{BGColor: &color.RGBA{R: 128, G: 0, B: 0, A: 255}})
grid.LineNumbers = true
grid.Whitespace = true

Expand Down
134 changes: 111 additions & 23 deletions widget/textgrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,46 @@ const (
textAreaNewLineSymbol = '↵'
)

var (
// TextGridStyleDefault is a default style for test grid cells
TextGridStyleDefault TextGridStyle
// TextGridStyleWhitespace is the style used for whitespace characters, if enabled
TextGridStyleWhitespace TextGridStyle
)

// define the types seperately to the var definition so the custom style API is not leaked in their instances.
func init() {
TextGridStyleDefault = &CustomTextGridStyle{}
TextGridStyleWhitespace = &CustomTextGridStyle{FGColor: theme.ButtonColor()}
}

// TextGridCell represents a single cell in a text grid.
// It has a rune for the text content and a style associated with it.
type TextGridCell struct {
Rune rune
TextColor color.Color
Rune rune
Style TextGridStyle
}

var (
whitespaceColor = theme.ButtonColor()
)
// TextGridStyle defines a style that can be applied to a TextGrid cell.
type TextGridStyle interface {
TextColor() color.Color
BackgroundColor() color.Color
}

// CustomTextGridStyle is a utility type for those not wanting to define their own style types.
type CustomTextGridStyle struct {
FGColor, BGColor color.Color
}

// TextColor is the color a cell should use for the text.
func (c *CustomTextGridStyle) TextColor() color.Color {
return c.FGColor
}

// BackgroundColor is the color a cell should use for the background.
func (c *CustomTextGridStyle) BackgroundColor() color.Color {
return c.BGColor
}

// TextGrid is a monospaced grid of characters.
// This is designed to be used by a text editor, code preview or terminal emulator.
Expand Down Expand Up @@ -109,6 +139,49 @@ func (t *TextGrid) SetRow(row int, content []TextGridCell) {
t.Refresh()
}

// SetStyle sets a grid style to the cell at named row and column
func (t *TextGrid) SetStyle(row, col int, style TextGridStyle) {
if row < 0 || col < 0 {
return
}
for len(t.Content) <= row {
t.Content = append(t.Content, []TextGridCell{})
}
content := t.Content[row]

for len(content) <= col {
content = append(content, TextGridCell{})
}
content[col].Style = style
}

// SetStyleRange sets a grid style to all the cells between the start row and column through to the end row and column.
func (t *TextGrid) SetStyleRange(startRow, startCol, endRow, endCol int, style TextGridStyle) {
if startRow == endRow {
for col := startCol; col <= endCol; col++ {
t.SetStyle(startRow, col, style)
}
return
}

// first row
for col := startCol; col < len(t.Content[startRow]); col++ {
t.SetStyle(startRow, col, style)
}

// possible middle rows
for rowNum := startRow + 1; rowNum < endRow-1; rowNum++ {
for col := 0; col < len(t.Content[rowNum]); col++ {
t.SetStyle(rowNum, col, style)
}
}

// last row
for col := 0; col <= endCol; col++ {
t.SetStyle(endRow, col, style)
}
}

// CreateRenderer is a private method to Fyne which links this widget to it's renderer
func (t *TextGrid) CreateRenderer() fyne.WidgetRenderer {
t.ExtendBaseWidget(t)
Expand Down Expand Up @@ -148,31 +221,38 @@ func (t *textGridRender) appendTextCell(str rune) {
text := canvas.NewText(string(str), theme.TextColor())
text.TextStyle.Monospace = true

t.objects = append(t.objects, text)
bg := canvas.NewRectangle(color.Transparent)
t.objects = append(t.objects, bg, text)
}

func (t *textGridRender) setCellRune(str rune, pos int, cellFG color.Color) {
text := t.objects[pos].(*canvas.Text)
func (t *textGridRender) setCellRune(str rune, pos int, style TextGridStyle) {
rect := t.objects[pos*2].(*canvas.Rectangle)
text := t.objects[pos*2+1].(*canvas.Text)
if str == 0 {
text.Text = " "
} else {
text.Text = string(str)
}

fg := theme.TextColor()
if cellFG != nil {
fg = cellFG
if style != nil && style.TextColor() != nil {
fg = style.TextColor()
}

text.Color = fg

bg := color.Color(color.Transparent)
if style != nil && style.BackgroundColor() != nil {
bg = style.BackgroundColor()
}
rect.FillColor = bg
}

func (t *textGridRender) ensureGrid() {
cellCount := t.cols * t.rows
if len(t.objects) == cellCount {
if len(t.objects) == cellCount*2 {
return
}
for i := len(t.objects); i < cellCount; i++ {
for i := len(t.objects); i < cellCount*2; i += 2 {
t.appendTextCell(' ')
}
}
Expand All @@ -189,16 +269,16 @@ func (t *textGridRender) refreshGrid() {
if t.text.LineNumbers {
lineStr := []rune(fmt.Sprintf("%d", line))
for c := 0; c < len(lineStr); c++ {
t.setCellRune(lineStr[c], x, whitespaceColor) // line numbers
t.setCellRune(lineStr[c], x, TextGridStyleWhitespace) // line numbers
i++
x++
}
for ; i < t.lineCountWidth(); i++ {
t.setCellRune(' ', x, whitespaceColor) // padding space
t.setCellRune(' ', x, TextGridStyleWhitespace) // padding space
x++
}

t.setCellRune('|', x, whitespaceColor) // last space
t.setCellRune('|', x, TextGridStyleWhitespace) // last space
i++
x++
}
Expand All @@ -207,27 +287,33 @@ func (t *textGridRender) refreshGrid() {
continue
}
if t.text.Whitespace && r.Rune == ' ' {
t.setCellRune(textAreaSpaceSymbol, x, whitespaceColor) // whitespace char
if r.Style != nil && r.Style.BackgroundColor() != nil {
whitespaceBG := &CustomTextGridStyle{FGColor: TextGridStyleWhitespace.TextColor(),
BGColor: r.Style.BackgroundColor()}
t.setCellRune(textAreaSpaceSymbol, x, whitespaceBG) // whitespace char
} else {
t.setCellRune(textAreaSpaceSymbol, x, TextGridStyleWhitespace) // whitespace char
}
} else {
t.setCellRune(r.Rune, x, r.TextColor) // regular char
t.setCellRune(r.Rune, x, r.Style) // regular char
}
i++
x++
}
if t.text.Whitespace && i < t.cols && rowIndex < len(t.text.Content)-1 {
t.setCellRune(textAreaNewLineSymbol, x, whitespaceColor) // newline
t.setCellRune(textAreaNewLineSymbol, x, TextGridStyleWhitespace) // newline
i++
x++
}
for ; i < t.cols; i++ {
t.setCellRune(' ', x, nil) // blanks
t.setCellRune(' ', x, TextGridStyleDefault) // blanks
x++
}

line++
}
for ; x < len(t.objects); x++ {
t.setCellRune(' ', x, nil) // blank lines?
for ; x < len(t.objects)/2; x++ {
t.setCellRune(' ', x, TextGridStyleDefault) // blank lines?
}
canvas.Refresh(t.text)
}
Expand Down Expand Up @@ -264,8 +350,10 @@ func (t *textGridRender) Layout(size fyne.Size) {
cellPos := fyne.NewPos(0, 0)
for y := 0; y < t.rows; y++ {
for x := 0; x < t.cols; x++ {
t.objects[i].Move(cellPos)
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++
}
Expand Down
38 changes: 30 additions & 8 deletions widget/textgrid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,34 @@ func TestTextGrid_Rows(t *testing.T) {
assert.Equal(t, 2, len(grid.Content[0]))
}

func TestTextGrid_SetStyle(t *testing.T) {
grid := NewTextGridFromString("Abc")
grid.SetStyle(0, 1, &CustomTextGridStyle{FGColor: color.White, BGColor: color.Black})

assert.Nil(t, grid.Content[0][0].Style)
assert.Equal(t, color.White, grid.Content[0][1].Style.TextColor())
assert.Equal(t, color.Black, grid.Content[0][1].Style.BackgroundColor())
}

func TestTextGrid_SetStyleRange(t *testing.T) {
grid := NewTextGridFromString("Ab\ncd")
grid.SetStyleRange(0, 1, 1, 0, &CustomTextGridStyle{FGColor: color.White, BGColor: color.Black})

assert.Nil(t, grid.Content[0][0].Style)
assert.Equal(t, color.White, grid.Content[0][1].Style.TextColor())
assert.Equal(t, color.Black, grid.Content[0][1].Style.BackgroundColor())
assert.Equal(t, color.White, grid.Content[1][0].Style.TextColor())
assert.Equal(t, color.Black, grid.Content[1][0].Style.BackgroundColor())
assert.Nil(t, grid.Content[1][1].Style)
}

func TestTextGrid_CreateRendererRows(t *testing.T) {
grid := NewTextGrid()
grid.Resize(fyne.NewSize(56, 22))
rend := test.WidgetRenderer(grid).(*textGridRender)
rend.Refresh()

assert.Equal(t, 4, len(rend.objects))
assert.Equal(t, 8, len(rend.objects))
}

func TestTextGridRender_Size(t *testing.T) {
Expand All @@ -62,21 +83,22 @@ func TestTextGridRender_Whitespace(t *testing.T) {

assert.Equal(t, 4, rend.cols)
assert.Equal(t, 2, rend.rows)
assert.Equal(t, string(textAreaSpaceSymbol), rend.objects[1].(*canvas.Text).Text) // col 2 is space
assert.Equal(t, string(textAreaNewLineSymbol), rend.objects[3].(*canvas.Text).Text) // col 4 is newline
assert.NotEqual(t, string(textAreaNewLineSymbol), rend.objects[5].(*canvas.Text).Text) // no newline on end of content
// indexes of text are at n*2+1 due to bg rects appearing before letter objects
assert.Equal(t, string(textAreaSpaceSymbol), rend.objects[3].(*canvas.Text).Text) // col 1 is space
assert.Equal(t, string(textAreaNewLineSymbol), rend.objects[7].(*canvas.Text).Text) // col 3 is newline
assert.NotEqual(t, string(textAreaNewLineSymbol), rend.objects[11].(*canvas.Text).Text) // no newline on end of content
}

func TestTextGridRender_TextColor(t *testing.T) {
grid := NewTextGridFromString("Ab ")
grid.Content[0][1].TextColor = color.Black
grid.Content[0][1].Style = &CustomTextGridStyle{FGColor: color.Black}
grid.Whitespace = true
grid.Resize(fyne.NewSize(56, 22)) // causes refresh
rend := test.WidgetRenderer(grid).(*textGridRender)

assert.Equal(t, 4, rend.cols)
assert.Equal(t, 1, rend.rows)
assert.Equal(t, theme.TextColor(), rend.objects[0].(*canvas.Text).Color)
assert.Equal(t, color.Black, rend.objects[1].(*canvas.Text).Color)
assert.Equal(t, whitespaceColor, rend.objects[2].(*canvas.Text).Color)
assert.Equal(t, theme.TextColor(), rend.objects[1].(*canvas.Text).Color)
assert.Equal(t, color.Black, rend.objects[3].(*canvas.Text).Color)
assert.Equal(t, TextGridStyleWhitespace.TextColor(), rend.objects[5].(*canvas.Text).Color)
}

0 comments on commit cb7580f

Please sign in to comment.