|
|
|
@ -7,6 +7,7 @@ import (
|
|
|
|
|
"strings" |
|
|
|
|
|
|
|
|
|
"github.com/gdamore/tcell" |
|
|
|
|
runewidth "github.com/mattn/go-runewidth" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Text alignment within a box.
|
|
|
|
@ -90,40 +91,69 @@ func init() {
|
|
|
|
|
// not exceeding that box. "align" is one of AlignLeft, AlignCenter, or
|
|
|
|
|
// AlignRight. The screen's background color will not be changed.
|
|
|
|
|
//
|
|
|
|
|
// Returns the number of actual runes printed.
|
|
|
|
|
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) int { |
|
|
|
|
// Returns the number of actual runes printed and the actual width used for the
|
|
|
|
|
// printed runes.
|
|
|
|
|
func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) { |
|
|
|
|
// We deal with runes, not with bytes.
|
|
|
|
|
runes := []rune(text) |
|
|
|
|
if maxWidth < 0 { |
|
|
|
|
return 0 |
|
|
|
|
return 0, 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AlignCenter is split into two parts.
|
|
|
|
|
// AlignCenter is a special case.
|
|
|
|
|
if align == AlignCenter { |
|
|
|
|
half := len(runes) / 2 |
|
|
|
|
halfWidth := maxWidth / 2 |
|
|
|
|
return Print(screen, string(runes[:half]), x, y, halfWidth, AlignRight, color) + |
|
|
|
|
Print(screen, string(runes[half:]), x+halfWidth, y, maxWidth-halfWidth, AlignLeft, color) |
|
|
|
|
width := runewidth.StringWidth(text) |
|
|
|
|
if width == maxWidth { |
|
|
|
|
// Use the exact space.
|
|
|
|
|
return Print(screen, text, x, y, maxWidth, AlignLeft, color) |
|
|
|
|
} else if width < maxWidth { |
|
|
|
|
// We have more space than we need.
|
|
|
|
|
half := (maxWidth - width) / 2 |
|
|
|
|
return Print(screen, text, x+half, y, maxWidth-half, AlignLeft, color) |
|
|
|
|
} else { |
|
|
|
|
// Chop off runes until we have a perfect fit.
|
|
|
|
|
var start, choppedLeft, choppedRight int |
|
|
|
|
ru := runes |
|
|
|
|
for len(ru) > 0 && width-choppedLeft-choppedRight > maxWidth { |
|
|
|
|
leftWidth := runewidth.RuneWidth(ru[0]) |
|
|
|
|
rightWidth := runewidth.RuneWidth(ru[len(ru)-1]) |
|
|
|
|
if choppedLeft < choppedRight { |
|
|
|
|
start++ |
|
|
|
|
choppedLeft += leftWidth |
|
|
|
|
ru = ru[1:] |
|
|
|
|
} else { |
|
|
|
|
choppedRight += rightWidth |
|
|
|
|
ru = ru[:len(ru)-1] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return Print(screen, string(ru), x, y, maxWidth, AlignLeft, color) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Draw text.
|
|
|
|
|
drawn := 0 |
|
|
|
|
drawnWidth := 0 |
|
|
|
|
for pos, ch := range runes { |
|
|
|
|
if pos >= maxWidth { |
|
|
|
|
chWidth := runewidth.RuneWidth(ch) |
|
|
|
|
if drawnWidth+chWidth > maxWidth { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
finalX := x + pos |
|
|
|
|
finalX := x + drawnWidth |
|
|
|
|
if align == AlignRight { |
|
|
|
|
ch = runes[len(runes)-1-pos] |
|
|
|
|
finalX = x + maxWidth - 1 - pos |
|
|
|
|
finalX = x + maxWidth - chWidth - drawnWidth |
|
|
|
|
} |
|
|
|
|
_, _, style, _ := screen.GetContent(finalX, y) |
|
|
|
|
style = style.Foreground(color) |
|
|
|
|
screen.SetContent(finalX, y, ch, nil, style) |
|
|
|
|
for offset := 0; offset < chWidth; offset++ { |
|
|
|
|
// To avoid undesired effects, we place the same character in all cells.
|
|
|
|
|
screen.SetContent(finalX+offset, y, ch, nil, style) |
|
|
|
|
} |
|
|
|
|
drawn++ |
|
|
|
|
drawnWidth += chWidth |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return drawn |
|
|
|
|
return drawn, drawnWidth |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// PrintSimple prints white text to the screen at the given position.
|
|
|
|
@ -132,8 +162,8 @@ func PrintSimple(screen tcell.Screen, text string, x, y int) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// WordWrap splits a text such that each resulting line does not exceed the
|
|
|
|
|
// given width. Possible split points are after commas, dots, dashes, and any
|
|
|
|
|
// whitespace. Whitespace at split points will be dropped.
|
|
|
|
|
// given screen width. Possible split points are after commas, dots, dashes,
|
|
|
|
|
// and any whitespace. Whitespace at split points will be dropped.
|
|
|
|
|
//
|
|
|
|
|
// Text is always split at newline characters ('\n').
|
|
|
|
|
func WordWrap(text string, width int) (lines []string) { |
|
|
|
@ -146,6 +176,8 @@ func WordWrap(text string, width int) (lines []string) {
|
|
|
|
|
text = strings.TrimSpace(text) |
|
|
|
|
|
|
|
|
|
for pos, ch := range text { |
|
|
|
|
chWidth := runewidth.RuneWidth(ch) |
|
|
|
|
|
|
|
|
|
if !evaluatingCandidate && x >= width { |
|
|
|
|
// We've exceeded the width, we must split.
|
|
|
|
|
if candidate >= 0 { |
|
|
|
@ -190,8 +222,8 @@ func WordWrap(text string, width int) (lines []string) {
|
|
|
|
|
countAfterCandidate = 0 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
x++ |
|
|
|
|
countAfterCandidate++ |
|
|
|
|
x += chWidth |
|
|
|
|
countAfterCandidate += chWidth |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Process remaining text.
|
|
|
|
|