mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
f35fe407b3
We're about to remove the extra padding from Editor. To do that, the clipping must account for text drawing that lie outside the viewport. With accurate clip bounds, use it for Label as well. Signed-off-by: Elias Naur <mail@eliasnaur.com>
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
// Package text implements text widgets.
|
|
package text
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"unicode/utf8"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/layout"
|
|
"gioui.org/op"
|
|
"gioui.org/op/paint"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// Label is a widget for laying out and drawing text.
|
|
type Label struct {
|
|
Font Font
|
|
|
|
// Material is a macro recording the material to draw the
|
|
// text. Use a ColorOp for colored text.
|
|
Material op.MacroOp
|
|
// Alignment specify the text alignment.
|
|
Alignment Alignment
|
|
// Text is the string to draw.
|
|
Text string
|
|
// MaxLines limits the number of lines. Zero means no limit.
|
|
MaxLines int
|
|
}
|
|
|
|
type lineIterator struct {
|
|
Lines []Line
|
|
Clip image.Rectangle
|
|
Alignment Alignment
|
|
Width int
|
|
Offset image.Point
|
|
|
|
y, prevDesc fixed.Int26_6
|
|
}
|
|
|
|
const inf = 1e6
|
|
|
|
func (l *lineIterator) Next() (String, f32.Point, bool) {
|
|
for len(l.Lines) > 0 {
|
|
line := l.Lines[0]
|
|
l.Lines = l.Lines[1:]
|
|
x := align(l.Alignment, line.Width, l.Width) + fixed.I(l.Offset.X)
|
|
l.y += l.prevDesc + line.Ascent
|
|
l.prevDesc = line.Descent
|
|
// Align baseline and line start to the pixel grid.
|
|
off := fixed.Point26_6{X: fixed.I(x.Floor()), Y: fixed.I(l.y.Ceil())}
|
|
l.y = off.Y
|
|
off.Y += fixed.I(l.Offset.Y)
|
|
if (off.Y + line.Bounds.Min.Y).Floor() > l.Clip.Max.Y {
|
|
break
|
|
}
|
|
if (off.Y + line.Bounds.Max.Y).Ceil() < l.Clip.Min.Y {
|
|
continue
|
|
}
|
|
str := line.Text
|
|
for len(str.Advances) > 0 {
|
|
adv := str.Advances[0]
|
|
if (off.X + adv + line.Bounds.Max.X - line.Width).Ceil() >= l.Clip.Min.X {
|
|
break
|
|
}
|
|
off.X += adv
|
|
_, s := utf8.DecodeRuneInString(str.String)
|
|
str.String = str.String[s:]
|
|
str.Advances = str.Advances[1:]
|
|
}
|
|
n := 0
|
|
endx := off.X
|
|
for i, adv := range str.Advances {
|
|
if (endx + line.Bounds.Min.X).Floor() > l.Clip.Max.X {
|
|
str.String = str.String[:n]
|
|
str.Advances = str.Advances[:i]
|
|
break
|
|
}
|
|
_, s := utf8.DecodeRuneInString(str.String[n:])
|
|
n += s
|
|
endx += adv
|
|
}
|
|
offf := f32.Point{X: float32(off.X) / 64, Y: float32(off.Y) / 64}
|
|
return str, offf, true
|
|
}
|
|
return String{}, f32.Point{}, false
|
|
}
|
|
|
|
func (l Label) Layout(gtx *layout.Context, s *Shaper) {
|
|
cs := gtx.Constraints
|
|
textLayout := s.Layout(gtx, l.Font, l.Text, LayoutOptions{MaxWidth: cs.Width.Max})
|
|
lines := textLayout.Lines
|
|
if max := l.MaxLines; max > 0 && len(lines) > max {
|
|
lines = lines[:max]
|
|
}
|
|
dims := linesDimens(lines)
|
|
dims.Size = cs.Constrain(dims.Size)
|
|
clip := textPadding(lines)
|
|
clip.Max = clip.Max.Add(dims.Size)
|
|
it := lineIterator{
|
|
Lines: lines,
|
|
Clip: clip,
|
|
Alignment: l.Alignment,
|
|
Width: dims.Size.X,
|
|
}
|
|
for {
|
|
str, off, ok := it.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
lclip := toRectF(clip).Sub(off)
|
|
var stack op.StackOp
|
|
stack.Push(gtx.Ops)
|
|
op.TransformOp{}.Offset(off).Add(gtx.Ops)
|
|
s.Shape(gtx, l.Font, str).Add(gtx.Ops)
|
|
// Set a default color in case the material is empty.
|
|
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
|
|
l.Material.Add(gtx.Ops)
|
|
paint.PaintOp{Rect: lclip}.Add(gtx.Ops)
|
|
stack.Pop()
|
|
}
|
|
gtx.Dimensions = dims
|
|
}
|
|
|
|
func toRectF(r image.Rectangle) f32.Rectangle {
|
|
return f32.Rectangle{
|
|
Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)},
|
|
Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)},
|
|
}
|
|
}
|
|
|
|
func textPadding(lines []Line) (padding image.Rectangle) {
|
|
if len(lines) == 0 {
|
|
return
|
|
}
|
|
first := lines[0]
|
|
if d := first.Ascent + first.Bounds.Min.Y; d < 0 {
|
|
padding.Min.Y = d.Ceil()
|
|
}
|
|
last := lines[len(lines)-1]
|
|
if d := last.Bounds.Max.Y - last.Descent; d > 0 {
|
|
padding.Max.Y = d.Ceil()
|
|
}
|
|
if d := first.Bounds.Min.X; d < 0 {
|
|
padding.Min.X = d.Ceil()
|
|
}
|
|
if d := first.Bounds.Max.X - first.Width; d > 0 {
|
|
padding.Max.X = d.Ceil()
|
|
}
|
|
return
|
|
}
|