131 lines
3.2 KiB
Go
131 lines
3.2 KiB
Go
|
package ui
|
||
|
|
||
|
import (
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"opslag.de/schobers/geom"
|
||
|
)
|
||
|
|
||
|
type Paragraph struct {
|
||
|
Label
|
||
|
|
||
|
width float32
|
||
|
lines CachedValue
|
||
|
}
|
||
|
|
||
|
func BuildParagraph(text string, fn func(*Paragraph)) *Paragraph {
|
||
|
var p = &Paragraph{}
|
||
|
p.Text = text
|
||
|
if fn != nil {
|
||
|
fn(p)
|
||
|
}
|
||
|
return p
|
||
|
}
|
||
|
|
||
|
func fastFormatFloat32(f float32) string { return strconv.FormatFloat(float64(f), 'b', 32, 32) }
|
||
|
|
||
|
func (p *Paragraph) desiredSize(ctx Context) interface{} {
|
||
|
fontName := p.FontName(ctx)
|
||
|
font := ctx.Fonts().Font(fontName)
|
||
|
|
||
|
pad := ctx.Style().Dimensions.TextPadding
|
||
|
lines := p.splitInLines(ctx, p.width-2*pad)
|
||
|
return geom.PtF32(p.width, float32(len(lines))*font.Height()+2*pad)
|
||
|
}
|
||
|
|
||
|
func (p *Paragraph) hashTextArranged(ctx Context) string {
|
||
|
return p.FontName(ctx) + p.Text + fastFormatFloat32(p.Bounds().Dx())
|
||
|
}
|
||
|
|
||
|
func (p *Paragraph) hashTextDesired(ctx Context) string {
|
||
|
return p.FontName(ctx) + p.Text + fastFormatFloat32(p.width)
|
||
|
}
|
||
|
|
||
|
func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
|
||
|
fontName := p.FontName(ctx)
|
||
|
font := ctx.Fonts().Font(fontName)
|
||
|
|
||
|
spaces := func(s string) []int { // creates a slice with indices where spaces can be found in string s
|
||
|
var spaces []int
|
||
|
offset := 0
|
||
|
for {
|
||
|
space := strings.Index(s[offset:], " ")
|
||
|
if space == -1 {
|
||
|
return spaces
|
||
|
}
|
||
|
offset += space
|
||
|
spaces = append(spaces, offset)
|
||
|
offset++
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fit := func(s string) string { // tries to fit as much of string s in width space.
|
||
|
if font.WidthOf(s) < width {
|
||
|
return s
|
||
|
}
|
||
|
spaces := spaces(s)
|
||
|
// removes one word (delimited by spaces) at a time and tries until the result fits.
|
||
|
for split := len(spaces) - 1; split >= 0; split-- {
|
||
|
clipped := s[:spaces[split]]
|
||
|
if font.WidthOf(clipped) < width {
|
||
|
return clipped
|
||
|
}
|
||
|
}
|
||
|
// nothing fits (returns the whole string)...
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
var lines []string
|
||
|
for _, line := range strings.Split(p.Text, "\n") {
|
||
|
if len(line) == 0 {
|
||
|
lines = append(lines, line)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for len(line) > 0 {
|
||
|
clipped := fit(line)
|
||
|
lines = append(lines, clipped)
|
||
|
line = strings.TrimLeft(line[len(clipped):], " ")
|
||
|
}
|
||
|
}
|
||
|
return lines
|
||
|
}
|
||
|
|
||
|
func (p *Paragraph) updateLines(ctx Context) interface{} {
|
||
|
pad := ctx.Style().Dimensions.TextPadding
|
||
|
return p.splitInLines(ctx, p.Bounds().Dx()-2*pad)
|
||
|
}
|
||
|
|
||
|
func (p *Paragraph) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
|
||
|
// stores the given width, is used when calculating the new desired size (and thus used in the hash method as well)
|
||
|
p.width = size.X
|
||
|
return p.desired.GetContext(ctx, p.desiredSize, p.hashTextDesired).(geom.PointF32)
|
||
|
}
|
||
|
|
||
|
func (p *Paragraph) Render(ctx Context) {
|
||
|
p.RenderBackground(ctx)
|
||
|
|
||
|
pad := ctx.Style().Dimensions.TextPadding
|
||
|
width := p.Bounds().Dx() - 2*pad
|
||
|
lines := p.lines.GetContext(ctx, p.updateLines, p.hashTextArranged).([]string)
|
||
|
|
||
|
fontColor := p.TextColor(ctx)
|
||
|
fontName := p.FontName(ctx)
|
||
|
fontHeight := ctx.Fonts().Font(fontName).Height()
|
||
|
bounds := p.bounds.Inset(pad)
|
||
|
|
||
|
left := bounds.Min.X
|
||
|
switch p.TextAlignment {
|
||
|
case AlignRight:
|
||
|
left = bounds.Max.X
|
||
|
case AlignCenter:
|
||
|
left += .5 * width
|
||
|
}
|
||
|
offset := bounds.Min.Y
|
||
|
for _, line := range lines {
|
||
|
ctx.Fonts().TextAlign(fontName, geom.PtF32(left, offset), fontColor, line, p.TextAlignment)
|
||
|
offset += fontHeight
|
||
|
}
|
||
|
}
|