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"
|
"opslag.de/schobers/geom"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TextOverflow int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TextOverflowClip TextOverflow = iota
|
||||||
|
TextOverflowEllipsis
|
||||||
|
)
|
||||||
|
|
||||||
type Label struct {
|
type Label struct {
|
||||||
ControlBase
|
ControlBase
|
||||||
|
|
||||||
Text string
|
Text string
|
||||||
|
TextOverflow TextOverflow
|
||||||
DropShadow color.Color
|
DropShadow color.Color
|
||||||
|
|
||||||
desired CachedValue
|
desired CachedValue
|
||||||
@ -55,10 +63,15 @@ func (l *Label) getLabelTopLeft(ctx Context) geom.PointF32 {
|
|||||||
func (l *Label) Render(ctx Context) {
|
func (l *Label) Render(ctx Context) {
|
||||||
l.RenderBackground(ctx)
|
l.RenderBackground(ctx)
|
||||||
fontColor := l.TextColor(ctx)
|
fontColor := l.TextColor(ctx)
|
||||||
fontName := l.FontName(ctx)
|
font := l.ActualFont(ctx)
|
||||||
topLeft := l.getLabelTopLeft(ctx)
|
topLeft := l.getLabelTopLeft(ctx)
|
||||||
if l.DropShadow != nil {
|
text := l.Text
|
||||||
ctx.Fonts().TextAlign(fontName, topLeft.Add2D(1, 1), l.DropShadow, l.Text, l.TextAlignment)
|
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 {
|
func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
|
||||||
font := p.ActualFont(ctx)
|
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
|
var lines []string
|
||||||
for _, line := range strings.Split(p.Text, "\n") {
|
for _, line := range strings.Split(p.Text, "\n") {
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
@ -82,7 +51,7 @@ func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for len(line) > 0 {
|
for len(line) > 0 {
|
||||||
clipped := fit(line)
|
clipped := fitTextWord(font, line, width)
|
||||||
lines = append(lines, clipped)
|
lines = append(lines, clipped)
|
||||||
line = strings.TrimLeft(line[len(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