diff --git a/ui/label.go b/ui/label.go index 05d52fc..bb92df7 100644 --- a/ui/label.go +++ b/ui/label.go @@ -6,11 +6,19 @@ import ( "opslag.de/schobers/geom" ) +type TextOverflow int + +const ( + TextOverflowClip TextOverflow = iota + TextOverflowEllipsis +) + type Label struct { ControlBase - Text string - DropShadow color.Color + 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) } diff --git a/ui/paragraph.go b/ui/paragraph.go index 902347e..7e9897c 100644 --- a/ui/paragraph.go +++ b/ui/paragraph.go @@ -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):], " ") } diff --git a/ui/text.go b/ui/text.go new file mode 100644 index 0000000..712db29 --- /dev/null +++ b/ui/text.go @@ -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]] +}