Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tabs support #1836

Merged
merged 17 commits into from May 8, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions go.mod
Expand Up @@ -5,7 +5,7 @@ go 1.12
require (
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9
github.com/akavel/rsrc v0.8.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3
github.com/fsnotify/fsnotify v1.4.9
github.com/fyne-io/mobile v0.1.2
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
Expand All @@ -28,7 +28,6 @@ require (
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3
)

replace github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 => github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d
86 changes: 86 additions & 0 deletions internal/painter/drawer.go
@@ -0,0 +1,86 @@
package painter

import (
"image"
"image/draw"
"log"
"math"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)

// Drawer extends "golang.org/x/image/font" to add support for tabs
// Drawer draws text on a destination image.
//
// A Drawer is not safe for concurrent use by multiple goroutines, since its
// Face is not.
type Drawer struct {
adrianre12 marked this conversation as resolved.
Show resolved Hide resolved
font.Drawer
}

func tabStop(f font.Face, x fixed.Int26_6) fixed.Int26_6 {
spacew, ok := f.GlyphAdvance(' ')
if !ok {
log.Print("Failed to find space width for tab")
return x
}
tabWidth := fyne.CurrentApp().Settings().Theme().Size(theme.SizeNameTabWidth)
tabw := spacew * fixed.Int26_6(tabWidth)
tabs, _ := math.Modf(float64((x + tabw) / tabw))
return tabw * fixed.Int26_6(tabs)
}

// DrawString draws s at the dot and advances the dot's location.
// Tabs are translated into a dot location change.
func (d *Drawer) DrawString(s string) {
prevC := rune(-1)
for _, c := range s {
if prevC >= 0 {
d.Dot.X += d.Face.Kern(prevC, c)
}
if c == '\t' {
d.Dot.X = tabStop(d.Face, d.Dot.X)
} else {
dr, mask, maskp, a, ok := d.Face.Glyph(d.Dot, c)
if !ok {
// TODO: is falling back on the U+FFFD glyph the responsibility of
adrianre12 marked this conversation as resolved.
Show resolved Hide resolved
// the Drawer or the Face?
// TODO: set prevC = '\ufffd'?
continue
}
draw.DrawMask(d.Dst, dr, d.Src, image.Point{}, mask, maskp, draw.Over)
d.Dot.X += a
}

prevC = c
}
}

// MeasureString returns how far dot would advance by drawing s with f.
// Tabs are translated into a dot location change.
func MeasureString(f font.Face, s string) (advance fixed.Int26_6) {
prevC := rune(-1)
for _, c := range s {
if prevC >= 0 {
advance += f.Kern(prevC, c)
}
if c == '\t' {
advance = tabStop(f, advance)
} else {
a, ok := f.GlyphAdvance(c)
if !ok {
// TODO: is falling back on the U+FFFD glyph the responsibility of
// the Drawer or the Face?
// TODO: set prevC = '\ufffd'?
continue
}
advance += a
}

prevC = c
}
return advance
}
2 changes: 1 addition & 1 deletion internal/painter/font.go
Expand Up @@ -31,7 +31,7 @@ func RenderedTextSize(text string, size float32, style fyne.TextStyle) fyne.Size
opts.DPI = TextDPI

face := CachedFontFace(style, &opts)
advance := font.MeasureString(face, text)
advance := MeasureString(face, text)

return fyne.NewSize(float32(advance.Ceil()), float32(face.Metrics().Height.Ceil()))
}
Expand Down
3 changes: 1 addition & 2 deletions internal/painter/gl/gl_common.go
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/goki/freetype"
"github.com/goki/freetype/truetype"
"golang.org/x/image/font"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
Expand Down Expand Up @@ -84,7 +83,7 @@ func (p *glPainter) newGlTextTexture(obj fyne.CanvasObject) Texture {
opts.DPI = float64(painter.TextDPI * p.texScale)
face := painter.CachedFontFace(text.TextStyle, &opts)

d := font.Drawer{}
d := painter.Drawer{}
d.Dst = img
d.Src = &image.Uniform{C: text.Color}
d.Face = face
Expand Down
3 changes: 1 addition & 2 deletions internal/painter/software/draw.go
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/goki/freetype"
"github.com/goki/freetype/truetype"
"golang.org/x/image/draw"
"golang.org/x/image/font"
)

type gradient interface {
Expand Down Expand Up @@ -145,7 +144,7 @@ func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.N
opts.DPI = painter.TextDPI
face := painter.CachedFontFace(text.TextStyle, &opts)

d := font.Drawer{}
d := painter.Drawer{}
d.Dst = txtImg
d.Src = &image.Uniform{C: text.Color}
d.Face = face
Expand Down
15 changes: 8 additions & 7 deletions internal/test/util_test.go
Expand Up @@ -8,10 +8,10 @@ import (
"os"
"testing"

"fyne.io/fyne/v2/internal/painter"
"github.com/goki/freetype"
"github.com/goki/freetype/truetype"
"github.com/stretchr/testify/require"
"golang.org/x/image/font"

"fyne.io/fyne/v2/internal/test"
"fyne.io/fyne/v2/theme"
Expand All @@ -28,12 +28,13 @@ func TestAssertImageMatches(t *testing.T) {
opts := truetype.Options{Size: 20, DPI: 96}
f, _ := truetype.Parse(theme.TextFont().Content())
face := truetype.NewFace(f, &opts)
d := font.Drawer{
Dst: txtImg,
Src: image.NewUniform(color.Black),
Face: face,
Dot: freetype.Pt(0, 50-face.Metrics().Descent.Ceil()),
}

d := painter.Drawer{}
d.Dst = txtImg
d.Src = image.NewUniform(color.Black)
d.Face = face
d.Dot = freetype.Pt(0, 50-face.Metrics().Descent.Ceil())

d.DrawString("Hello!")
draw.Draw(img, bounds, txtImg, image.Point{}, draw.Over)

Expand Down
3 changes: 1 addition & 2 deletions test/testdriver.go
Expand Up @@ -13,7 +13,6 @@ import (
"fyne.io/fyne/v2/storage/repository"

"github.com/goki/freetype/truetype"
"golang.org/x/image/font"
)

// SoftwarePainter describes a simple type that can render canvases
Expand Down Expand Up @@ -107,7 +106,7 @@ func (d *testDriver) RenderedTextSize(text string, size float32, style fyne.Text
opts.DPI = painter.TextDPI

face := painter.CachedFontFace(style, &opts)
advance := font.MeasureString(face, text)
advance := painter.MeasureString(face, text)

sws := fyne.NewSize(float32(advance.Ceil()), float32(face.Metrics().Height.Ceil()))
gls := painter.RenderedTextSize(text, size, style)
Expand Down
1 change: 1 addition & 0 deletions test/theme.go
Expand Up @@ -51,6 +51,7 @@ func Theme() fyne.Theme {
theme.SizeNameScrollBarSmall: float32(3),
theme.SizeNameSeparatorThickness: float32(1),
theme.SizeNameText: float32(14),
theme.SizeNameTabWidth: float32(4),
theme.SizeNameInputBorder: float32(2),
},
}
Expand Down
7 changes: 7 additions & 0 deletions theme/theme.go
Expand Up @@ -143,6 +143,11 @@ const (
// Since: 2.0
SizeNameSeparatorThickness fyne.ThemeSizeName = "separator"

// SizeNameTabWidth is the name of theme lookup for the width of tabs in spaces.
//
// Since: 2.0
SizeNameTabWidth fyne.ThemeSizeName = "tabWidth"

// SizeNameText is the name of theme lookup for text size.
//
// Since: 2.0
Expand Down Expand Up @@ -327,6 +332,8 @@ func (t *builtinTheme) Size(s fyne.ThemeSizeName) float32 {
return 11
case SizeNameInputBorder:
return 2
case SizeNameTabWidth:
adrianre12 marked this conversation as resolved.
Show resolved Hide resolved
return 4
default:
return 0
}
Expand Down
5 changes: 5 additions & 0 deletions theme/theme_test.go
Expand Up @@ -99,6 +99,11 @@ func Test_TextSize(t *testing.T) {
assert.Equal(t, DarkTheme().Size(SizeNameText), TextSize(), "wrong text size")
}

func Test_TabWidth(t *testing.T) {
fyne.CurrentApp().Settings().SetTheme(DarkTheme())
assert.Equal(t, DarkTheme().Size(SizeNameTabWidth), current().Size(SizeNameTabWidth), "wrong tab width")
}

func Test_TextFont(t *testing.T) {
fyne.CurrentApp().Settings().SetTheme(DarkTheme())
expect := "NotoSans-Regular.ttf"
Expand Down
2 changes: 1 addition & 1 deletion widget/entry_internal_test.go
Expand Up @@ -241,7 +241,7 @@ func TestEntry_Tab(t *testing.T) {
r := cache.Renderer(e.textProvider()).(*textRenderer)
assert.Equal(t, 3, len(r.texts))
assert.Equal(t, "a", r.texts[0].Text)
assert.Equal(t, textTabIndent+"b", r.texts[1].Text)
assert.Equal(t, "\tb", r.texts[1].Text)

w := test.NewWindow(e)
w.Resize(fyne.NewSize(86, 86))
Expand Down
6 changes: 1 addition & 5 deletions widget/text.go
Expand Up @@ -14,8 +14,6 @@ import (

const (
passwordChar = "•"
// TODO move to complete tab handling, for now we just indent this far statically
textTabIndent = " "
)

// textPresenter provides the widget specific information to a generic text provider
Expand Down Expand Up @@ -236,8 +234,6 @@ func (t *textProvider) lineSizeToColumn(col, row int) fyne.Size {
measureText := string(line[0:col])
if t.presenter.concealed() {
measureText = strings.Repeat(passwordChar, col)
} else {
measureText = strings.ReplaceAll(measureText, "\t", textTabIndent)
}

label := canvas.NewText(measureText, theme.ForegroundColor())
Expand Down Expand Up @@ -331,7 +327,7 @@ func (r *textRenderer) Refresh() {
if concealed {
line = strings.Repeat(passwordChar, len(row))
} else {
line = strings.ReplaceAll(string(row), "\t", textTabIndent)
line = string(row)
}

var textCanvas *canvas.Text
Expand Down
1 change: 1 addition & 0 deletions widget/textgrid.go
Expand Up @@ -15,6 +15,7 @@ const (
textAreaSpaceSymbol = '·'
textAreaTabSymbol = '→'
textAreaNewLineSymbol = '↵'
textTabIndent = " "
adrianre12 marked this conversation as resolved.
Show resolved Hide resolved
)

var (
Expand Down