mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
text: make text size implicit
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+41
-36
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user