text: make text size implicit

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-10-05 11:22:26 +02:00
parent f230036cad
commit b4a52d3010
4 changed files with 65 additions and 55 deletions
+41 -36
View File
@@ -23,7 +23,10 @@ import (
// Editor implements an editable and scrollable text area. // Editor implements an editable and scrollable text area.
type Editor struct { type Editor struct {
Face Face // Face defines the font and style of the text.
Face Face
// Size is the text size. If zero, a reasonable default is used.
Size unit.Value
Alignment Alignment Alignment Alignment
// SingleLine force the text to stay on a single line. // SingleLine force the text to stay on a single line.
// SingleLine also sets the scrolling direction to // SingleLine also sets the scrolling direction to
@@ -116,7 +119,7 @@ func (e *Editor) Event(gtx *layout.Context) (EditorEvent, bool) {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse, case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source == pointer.Touch: evt.Type == gesture.TypeClick && evt.Source == pointer.Touch:
e.blinkStart = gtx.Now() e.blinkStart = gtx.Now()
e.moveCoord(image.Point{ e.moveCoord(gtx, image.Point{
X: int(math.Round(float64(evt.Position.X))), X: int(math.Round(float64(evt.Position.X))),
Y: int(math.Round(float64(evt.Position.Y))), Y: int(math.Round(float64(evt.Position.Y))),
}) })
@@ -151,7 +154,7 @@ func (e *Editor) editorEvent(gtx *layout.Context) (EditorEvent, bool) {
return SubmitEvent{}, true return SubmitEvent{}, true
} }
} }
if e.command(ke) { if e.command(gtx, ke) {
e.scrollToCaret(gtx.Config) e.scrollToCaret(gtx.Config)
e.scroller.Stop() e.scroller.Stop()
} }
@@ -196,11 +199,11 @@ func (e *Editor) Layout(gtx *layout.Context) {
e.invalidate() e.invalidate()
} }
e.layout() e.layout(gtx)
lines, size := e.lines, e.dims.Size lines, size := e.lines, e.dims.Size
e.viewSize = cs.Constrain(size) e.viewSize = cs.Constrain(size)
carLine, _, carX, carY := e.layoutCaret() carLine, _, carX, carY := e.layoutCaret(gtx)
off := image.Point{ off := image.Point{
X: -e.scrollOff.X + e.padLeft, X: -e.scrollOff.X + e.padLeft,
@@ -229,6 +232,7 @@ func (e *Editor) Layout(gtx *layout.Context) {
paint.ColorOp{Color: color.RGBA{A: 0xaa}}.Add(gtx.Ops) paint.ColorOp{Color: color.RGBA{A: 0xaa}}.Add(gtx.Ops)
e.HintMaterial.Add(gtx.Ops) e.HintMaterial.Add(gtx.Ops)
} }
tsize := textSize(gtx, e.Size)
for { for {
str, lineOff, ok := e.it.Next() str, lineOff, ok := e.it.Next()
if !ok { if !ok {
@@ -237,7 +241,7 @@ func (e *Editor) Layout(gtx *layout.Context) {
var stack op.StackOp var stack op.StackOp
stack.Push(gtx.Ops) stack.Push(gtx.Ops)
op.TransformOp{}.Offset(lineOff).Add(gtx.Ops) op.TransformOp{}.Offset(lineOff).Add(gtx.Ops)
e.Face.Path(str).Add(gtx.Ops) e.Face.Path(tsize, str).Add(gtx.Ops)
paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops) paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops)
stack.Pop() stack.Pop()
} }
@@ -299,12 +303,12 @@ func (e *Editor) SetText(s string) {
e.prepend(s) e.prepend(s)
} }
func (e *Editor) layout() { func (e *Editor) layout(c unit.Converter) {
e.adjustScroll() e.adjustScroll()
if e.valid { if e.valid {
return return
} }
e.layoutText() e.layoutText(c)
e.valid = true e.valid = true
} }
@@ -340,8 +344,8 @@ func (e *Editor) adjustScroll() {
} }
} }
func (e *Editor) moveCoord(pos image.Point) { func (e *Editor) moveCoord(c unit.Converter, pos image.Point) {
e.layout() e.layout(c)
var ( var (
prevDesc fixed.Int26_6 prevDesc fixed.Int26_6
carLine int carLine int
@@ -356,15 +360,16 @@ func (e *Editor) moveCoord(pos image.Point) {
carLine++ carLine++
} }
x := fixed.I(pos.X + e.scrollOff.X - e.padLeft) x := fixed.I(pos.X + e.scrollOff.X - e.padLeft)
e.moveToLine(x, carLine) e.moveToLine(c, x, carLine)
} }
func (e *Editor) layoutText() { func (e *Editor) layoutText(c unit.Converter) {
s := e.rr.String() s := e.rr.String()
if s == "" { if s == "" {
s = e.Hint s = e.Hint
} }
textLayout := e.Face.Layout(s, LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth}) tsize := textSize(c, e.Size)
textLayout := e.Face.Layout(tsize, s, LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth})
lines := textLayout.Lines lines := textLayout.Lines
dims := linesDimens(lines) dims := linesDimens(lines)
for i := 0; i < len(lines)-1; i++ { for i := 0; i < len(lines)-1; i++ {
@@ -391,8 +396,8 @@ func (e *Editor) viewWidth() int {
return e.viewSize.X - e.padLeft - e.padRight return e.viewSize.X - e.padLeft - e.padRight
} }
func (e *Editor) layoutCaret() (carLine, carCol int, x fixed.Int26_6, y int) { func (e *Editor) layoutCaret(c unit.Converter) (carLine, carCol int, x fixed.Int26_6, y int) {
e.layout() e.layout(c)
var idx int var idx int
var prevDesc fixed.Int26_6 var prevDesc fixed.Int26_6
loop: loop:
@@ -450,9 +455,9 @@ func (e *Editor) prepend(s string) {
e.invalidate() e.invalidate()
} }
func (e *Editor) movePages(pages int) { func (e *Editor) movePages(c unit.Converter, pages int) {
e.layout() e.layout(c)
_, _, carX, carY := e.layoutCaret() _, _, carX, carY := e.layoutCaret(c)
y := carY + pages*e.viewSize.Y y := carY + pages*e.viewSize.Y
var ( var (
prevDesc fixed.Int26_6 prevDesc fixed.Int26_6
@@ -472,12 +477,12 @@ func (e *Editor) movePages(pages int) {
y2 += h y2 += h
carLine2++ carLine2++
} }
e.carXOff = e.moveToLine(carX+e.carXOff, carLine2) e.carXOff = e.moveToLine(c, carX+e.carXOff, carLine2)
} }
func (e *Editor) moveToLine(carX fixed.Int26_6, carLine2 int) fixed.Int26_6 { func (e *Editor) moveToLine(c unit.Converter, carX fixed.Int26_6, carLine2 int) fixed.Int26_6 {
e.layout() e.layout(c)
carLine, carCol, _, _ := e.layoutCaret() carLine, carCol, _, _ := e.layoutCaret(c)
if carLine2 < 0 { if carLine2 < 0 {
carLine2 = 0 carLine2 = 0
} }
@@ -534,8 +539,8 @@ func (e *Editor) moveRight() {
e.carXOff = 0 e.carXOff = 0
} }
func (e *Editor) moveStart() { func (e *Editor) moveStart(c unit.Converter) {
carLine, carCol, x, _ := e.layoutCaret() carLine, carCol, x, _ := e.layoutCaret(c)
advances := e.lines[carLine].Text.Advances advances := e.lines[carLine].Text.Advances
for i := carCol - 1; i >= 0; i-- { for i := carCol - 1; i >= 0; i-- {
_, s := e.rr.runeBefore(e.rr.caret) _, s := e.rr.runeBefore(e.rr.caret)
@@ -545,8 +550,8 @@ func (e *Editor) moveStart() {
e.carXOff = -x e.carXOff = -x
} }
func (e *Editor) moveEnd() { func (e *Editor) moveEnd(c unit.Converter) {
carLine, carCol, x, _ := e.layoutCaret() carLine, carCol, x, _ := e.layoutCaret(c)
l := e.lines[carLine] l := e.lines[carLine]
// Only move past the end of the last line // Only move past the end of the last line
end := 0 end := 0
@@ -565,7 +570,7 @@ func (e *Editor) moveEnd() {
func (e *Editor) scrollToCaret(c unit.Converter) { func (e *Editor) scrollToCaret(c unit.Converter) {
carWidth := e.caretWidth(c) carWidth := e.caretWidth(c)
carLine, _, x, y := e.layoutCaret() carLine, _, x, y := e.layoutCaret(c)
l := e.lines[carLine] l := e.lines[carLine]
if e.SingleLine { if e.SingleLine {
minx := (x - carWidth/2).Ceil() minx := (x - carWidth/2).Ceil()
@@ -588,7 +593,7 @@ func (e *Editor) scrollToCaret(c unit.Converter) {
} }
} }
func (e *Editor) command(k key.Event) bool { func (e *Editor) command(c unit.Converter, k key.Event) bool {
switch k.Name { switch k.Name {
case key.NameReturn, key.NameEnter: case key.NameReturn, key.NameEnter:
e.append("\n") e.append("\n")
@@ -597,23 +602,23 @@ func (e *Editor) command(k key.Event) bool {
case key.NameDeleteForward: case key.NameDeleteForward:
e.deleteRuneForward() e.deleteRuneForward()
case key.NameUpArrow: case key.NameUpArrow:
line, _, carX, _ := e.layoutCaret() line, _, carX, _ := e.layoutCaret(c)
e.carXOff = e.moveToLine(carX+e.carXOff, line-1) e.carXOff = e.moveToLine(c, carX+e.carXOff, line-1)
case key.NameDownArrow: case key.NameDownArrow:
line, _, carX, _ := e.layoutCaret() line, _, carX, _ := e.layoutCaret(c)
e.carXOff = e.moveToLine(carX+e.carXOff, line+1) e.carXOff = e.moveToLine(c, carX+e.carXOff, line+1)
case key.NameLeftArrow: case key.NameLeftArrow:
e.moveLeft() e.moveLeft()
case key.NameRightArrow: case key.NameRightArrow:
e.moveRight() e.moveRight()
case key.NamePageUp: case key.NamePageUp:
e.movePages(-1) e.movePages(c, -1)
case key.NamePageDown: case key.NamePageDown:
e.movePages(+1) e.movePages(c, +1)
case key.NameHome: case key.NameHome:
e.moveStart() e.moveStart(c)
case key.NameEnd: case key.NameEnd:
e.moveEnd() e.moveEnd(c)
default: default:
return false return false
} }
+14 -3
View File
@@ -12,14 +12,17 @@ import (
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
) )
// Label is a widget for laying out and drawing text. // Label is a widget for laying out and drawing text.
type Label struct { type Label struct {
// Face define the font and style of the text. // Face defines the font and style of the text.
Face Face Face Face
// Size is the text size. If zero, a default size is used.
Size unit.Value
// Material is a macro recording the material to draw the // Material is a macro recording the material to draw the
// text. Use a ColorOp for colored text. // text. Use a ColorOp for colored text.
Material op.MacroOp Material op.MacroOp
@@ -93,7 +96,8 @@ func (l *lineIterator) Next() (String, f32.Point, bool) {
func (l Label) Layout(gtx *layout.Context) { func (l Label) Layout(gtx *layout.Context) {
cs := gtx.Constraints cs := gtx.Constraints
textLayout := l.Face.Layout(l.Text, LayoutOptions{MaxWidth: cs.Width.Max}) tsize := textSize(gtx, l.Size)
textLayout := l.Face.Layout(tsize, l.Text, LayoutOptions{MaxWidth: cs.Width.Max})
lines := textLayout.Lines lines := textLayout.Lines
if max := l.MaxLines; max > 0 && len(lines) > max { if max := l.MaxLines; max > 0 && len(lines) > max {
lines = lines[:max] lines = lines[:max]
@@ -120,7 +124,7 @@ func (l Label) Layout(gtx *layout.Context) {
var stack op.StackOp var stack op.StackOp
stack.Push(gtx.Ops) stack.Push(gtx.Ops)
op.TransformOp{}.Offset(off).Add(gtx.Ops) op.TransformOp{}.Offset(off).Add(gtx.Ops)
l.Face.Path(str).Add(gtx.Ops) l.Face.Path(tsize, str).Add(gtx.Ops)
// Set a default color in case the material is empty. // Set a default color in case the material is empty.
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops) paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
l.Material.Add(gtx.Ops) l.Material.Add(gtx.Ops)
@@ -150,3 +154,10 @@ func textPadding(lines []Line) (padTop int, padBottom int) {
} }
return return
} }
func textSize(c unit.Converter, s unit.Value) float32 {
if s.V == 0 {
s = unit.Sp(12)
}
return float32(c.Px(s))
}
+2 -2
View File
@@ -48,9 +48,9 @@ type LayoutOptions struct {
type Face interface { type Face interface {
// Layout returns the text layout for a string given a set of // Layout returns the text layout for a string given a set of
// options. // options.
Layout(s string, opts LayoutOptions) *Layout Layout(size float32, s string, opts LayoutOptions) *Layout
// Path returns the ClipOp outline of a text recorded in a macro. // Path returns the ClipOp outline of a text recorded in a macro.
Path(s String) op.MacroOp Path(size float32, s String) op.MacroOp
} }
type Alignment uint8 type Alignment uint8
+8 -14
View File
@@ -14,7 +14,6 @@ import (
"gioui.org/op" "gioui.org/op"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text" "gioui.org/text"
"gioui.org/unit"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/font/sfnt" "golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
@@ -22,7 +21,6 @@ import (
// Faces is a cache of text layouts and paths. // Faces is a cache of text layouts and paths.
type Faces struct { type Faces struct {
config unit.Converter
faceCache map[faceKey]*Face faceCache map[faceKey]*Face
layoutCache map[layoutKey]cachedLayout layoutCache map[layoutKey]cachedLayout
pathCache map[pathKey]cachedPath pathCache map[pathKey]cachedPath
@@ -53,20 +51,17 @@ type pathKey struct {
type faceKey struct { type faceKey struct {
font *sfnt.Font font *sfnt.Font
size unit.Value
} }
// Face is a cached implementation of text.Face. // Face is a cached implementation of text.Face.
type Face struct { type Face struct {
faces *Faces faces *Faces
size unit.Value
font *opentype font *opentype
} }
// Reset the cache, discarding any measures or paths that // Reset the cache, discarding any measures or paths that
// haven't been used since the last call to Reset. // haven't been used since the last call to Reset.
func (f *Faces) Reset(c unit.Converter) { func (f *Faces) Reset() {
f.config = c
f.init() f.init()
for pk, p := range f.pathCache { for pk, p := range f.pathCache {
if !p.active { if !p.active {
@@ -86,16 +81,15 @@ func (f *Faces) Reset(c unit.Converter) {
} }
} }
// For returns a Face for the given font and size. // For returns a Face for the given font.
func (f *Faces) For(fnt *sfnt.Font, size unit.Value) *Face { func (f *Faces) For(fnt *sfnt.Font) *Face {
f.init() f.init()
fk := faceKey{fnt, size} fk := faceKey{fnt}
if f, exist := f.faceCache[fk]; exist { if f, exist := f.faceCache[fk]; exist {
return f return f
} }
face := &Face{ face := &Face{
faces: f, faces: f,
size: size,
font: &opentype{Font: fnt, Hinting: font.HintingFull}, font: &opentype{Font: fnt, Hinting: font.HintingFull},
} }
f.faceCache[fk] = face f.faceCache[fk] = face
@@ -111,8 +105,8 @@ func (f *Faces) init() {
f.layoutCache = make(map[layoutKey]cachedLayout) f.layoutCache = make(map[layoutKey]cachedLayout)
} }
func (f *Face) Layout(str string, opts text.LayoutOptions) *text.Layout { func (f *Face) Layout(size float32, str string, opts text.LayoutOptions) *text.Layout {
ppem := fixed.Int26_6(f.faces.config.Px(f.size) * 64) ppem := fixed.Int26_6(size * 64)
lk := layoutKey{ lk := layoutKey{
f: f.font.Font, f: f.font.Font,
ppem: ppem, ppem: ppem,
@@ -129,8 +123,8 @@ func (f *Face) Layout(str string, opts text.LayoutOptions) *text.Layout {
return l return l
} }
func (f *Face) Path(str text.String) op.MacroOp { func (f *Face) Path(size float32, str text.String) op.MacroOp {
ppem := fixed.Int26_6(f.faces.config.Px(f.size) * 64) ppem := fixed.Int26_6(size * 64)
pk := pathKey{ pk := pathKey{
f: f.font.Font, f: f.font.Font,
ppem: ppem, ppem: ppem,