Added text overflow to label.
Generalised text fitting (to width) and implemented binary search.
This commit is contained in:
parent
5dcecb8cc1
commit
de8ce3e7bc
21
ui/label.go
21
ui/label.go
@ -6,10 +6,18 @@ import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type TextOverflow int
|
||||
|
||||
const (
|
||||
TextOverflowClip TextOverflow = iota
|
||||
TextOverflowEllipsis
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
ControlBase
|
||||
|
||||
Text string
|
||||
TextOverflow TextOverflow
|
||||
DropShadow color.Color
|
||||
|
||||
desired CachedValue
|
||||
@ -55,10 +63,15 @@ func (l *Label) getLabelTopLeft(ctx Context) geom.PointF32 {
|
||||
func (l *Label) Render(ctx Context) {
|
||||
l.RenderBackground(ctx)
|
||||
fontColor := l.TextColor(ctx)
|
||||
fontName := l.FontName(ctx)
|
||||
font := l.ActualFont(ctx)
|
||||
topLeft := l.getLabelTopLeft(ctx)
|
||||
if l.DropShadow != nil {
|
||||
ctx.Fonts().TextAlign(fontName, topLeft.Add2D(1, 1), l.DropShadow, l.Text, l.TextAlignment)
|
||||
text := l.Text
|
||||
availableWidth := l.bounds.Dx()
|
||||
if l.TextOverflow == TextOverflowEllipsis {
|
||||
text = fitTextEllipsis(font, text, availableWidth)
|
||||
}
|
||||
ctx.Fonts().TextAlign(fontName, topLeft, fontColor, l.Text, l.TextAlignment)
|
||||
if l.DropShadow != nil {
|
||||
ctx.Renderer().TextAlign(font, topLeft.Add2D(1, 1), l.DropShadow, text, l.TextAlignment)
|
||||
}
|
||||
ctx.Renderer().TextAlign(font, topLeft, fontColor, text, l.TextAlignment)
|
||||
}
|
||||
|
@ -43,37 +43,6 @@ func (p *Paragraph) hashTextDesired(ctx Context) string {
|
||||
|
||||
func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
|
||||
font := p.ActualFont(ctx)
|
||||
|
||||
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 {
|
||||
@ -82,7 +51,7 @@ func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
|
||||
}
|
||||
|
||||
for len(line) > 0 {
|
||||
clipped := fit(line)
|
||||
clipped := fitTextWord(font, line, width)
|
||||
lines = append(lines, clipped)
|
||||
line = strings.TrimLeft(line[len(clipped):], " ")
|
||||
}
|
||||
|
68
ui/text.go
Normal file
68
ui/text.go
Normal file
@ -0,0 +1,68 @@
|
||||
package ui
|
||||
|
||||
import "strings"
|
||||
|
||||
// findOptimalFit tries to find the optimal (largest) fit for the interval [0..n] by doing a binary search.
|
||||
func findOptimalFit(n int, fits func(i int) bool) int {
|
||||
if n < 0 || fits(n) {
|
||||
return n
|
||||
}
|
||||
min, max := 0, n
|
||||
for {
|
||||
if min == max {
|
||||
if min == 0 && !fits(min) {
|
||||
return -1
|
||||
}
|
||||
return min
|
||||
}
|
||||
middle := (max + min + 1) / 2
|
||||
if fits(middle) {
|
||||
min = middle
|
||||
} else {
|
||||
max = middle - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findSpaces creates a slice with indices where spaces can be found in string s
|
||||
func findSpaces(s string) []int {
|
||||
var spaces []int
|
||||
offset := 0
|
||||
for {
|
||||
space := strings.Index(s[offset:], " ")
|
||||
if space == -1 {
|
||||
return spaces
|
||||
}
|
||||
offset += space
|
||||
spaces = append(spaces, offset)
|
||||
offset++
|
||||
}
|
||||
}
|
||||
|
||||
func fitTextEllipsis(font Font, text string, availableWidth float32) string {
|
||||
if font.WidthOf(text) <= availableWidth {
|
||||
return text
|
||||
}
|
||||
ellipsis := "..."
|
||||
availableWidth -= font.WidthOf(ellipsis)
|
||||
cut := findOptimalFit(len(text), func(i int) bool { return font.WidthOf(text[:i]) <= availableWidth })
|
||||
if cut == -1 {
|
||||
return ""
|
||||
}
|
||||
return text[:cut] + ellipsis
|
||||
}
|
||||
|
||||
// fitTextWord tries to fit as much of string s in width space.
|
||||
func fitTextWord(font Font, s string, availableWidth float32) string {
|
||||
if font.WidthOf(s) < availableWidth {
|
||||
return s
|
||||
}
|
||||
spaces := findSpaces(s)
|
||||
split := findOptimalFit(len(spaces)-1, func(i int) bool {
|
||||
return font.WidthOf(s[:spaces[i]]) <= availableWidth
|
||||
})
|
||||
if split == -1 {
|
||||
return s
|
||||
}
|
||||
return s[:spaces[split]]
|
||||
}
|
Loading…
Reference in New Issue
Block a user