2022-04-01 20:10:51 +02:00
|
|
|
package tui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
2022-04-02 13:01:41 +02:00
|
|
|
|
2023-04-24 14:17:02 +02:00
|
|
|
"git.milar.in/milarin/gmath"
|
2022-05-03 17:59:34 +02:00
|
|
|
"github.com/gdamore/tcell"
|
2022-04-03 16:29:01 +02:00
|
|
|
"github.com/mattn/go-runewidth"
|
2022-04-01 20:10:51 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// WriteString writes a whole string to the buffer at position (x,y)
|
|
|
|
// no word wrap is applied at all. If the string does not fit, it will be truncated
|
2022-04-02 13:01:41 +02:00
|
|
|
func WriteString(b *ViewBuffer, str string, style Style, x, y int) (width int) {
|
2022-04-01 20:10:51 +02:00
|
|
|
dx := x
|
|
|
|
for _, r := range str {
|
|
|
|
if dx >= b.Width() {
|
|
|
|
return
|
|
|
|
}
|
2022-04-02 13:01:41 +02:00
|
|
|
|
2022-04-01 20:10:51 +02:00
|
|
|
b.Set(dx, y, Rune{r, style})
|
2022-04-02 13:01:41 +02:00
|
|
|
dx += runeWidth(r)
|
2022-04-01 20:10:51 +02:00
|
|
|
}
|
2022-04-02 13:01:41 +02:00
|
|
|
return dx - x
|
2022-04-01 20:10:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteMultiLineString writes a multi-line string to the buffer at position (x,y)
|
|
|
|
// no word wrap is applied at all. If a line does not fit horizontally, it will be truncated
|
|
|
|
// All lines which do not fit vertically will be truncated as well
|
2022-04-02 13:01:41 +02:00
|
|
|
func WriteMultiLineString(b *ViewBuffer, str string, style Style, x, y int) (maxLineWidth, lineCount int) {
|
2022-04-01 20:10:51 +02:00
|
|
|
lines := strings.Split(str, "\n")
|
|
|
|
for dy, line := range lines {
|
|
|
|
if dy >= b.Height() {
|
|
|
|
return
|
|
|
|
}
|
2022-04-02 13:01:41 +02:00
|
|
|
lineWidth := WriteString(b, line, style, x, y+dy)
|
2023-04-24 14:17:02 +02:00
|
|
|
maxLineWidth = gmath.Max(maxLineWidth, lineWidth)
|
2022-04-02 13:01:41 +02:00
|
|
|
}
|
|
|
|
return maxLineWidth, len(lines)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MeasureString measures how much horizontal space str consumes when drawn to a buffer
|
|
|
|
func MeasureString(str string) (width int) {
|
|
|
|
dx := 0
|
|
|
|
for _, r := range str {
|
|
|
|
dx += runeWidth(r)
|
|
|
|
}
|
|
|
|
return dx
|
|
|
|
}
|
|
|
|
|
|
|
|
// MeasureString measures how much horizontal and vertical space str consumes when drawn to a buffer
|
|
|
|
func MeasureMultiLineString(str string) (maxLineWidth, lineCount int) {
|
|
|
|
lines := strings.Split(str, "\n")
|
|
|
|
for _, line := range lines {
|
|
|
|
lineWidth := MeasureString(line)
|
2023-04-24 14:17:02 +02:00
|
|
|
maxLineWidth = gmath.Max(maxLineWidth, lineWidth)
|
2022-04-02 13:01:41 +02:00
|
|
|
}
|
|
|
|
return maxLineWidth, len(lines)
|
|
|
|
}
|
|
|
|
|
|
|
|
func runeWidth(r rune) int {
|
2022-04-03 16:29:01 +02:00
|
|
|
return runewidth.RuneWidth(r)
|
2022-04-02 13:01:41 +02:00
|
|
|
//fmt.Println(r, width.LookupRune(r).Kind())
|
2022-04-03 16:29:01 +02:00
|
|
|
// switch width.LookupRune(r).Kind() {
|
|
|
|
// case width.EastAsianFullwidth:
|
|
|
|
// fallthrough
|
|
|
|
// case width.EastAsianWide:
|
|
|
|
// return 2
|
|
|
|
// default:
|
|
|
|
// return 1
|
|
|
|
// }
|
2022-04-02 13:01:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func iff[T any](condition bool, trueValue, falseValue T) T {
|
|
|
|
if condition {
|
|
|
|
return trueValue
|
2022-04-01 20:10:51 +02:00
|
|
|
}
|
2022-04-02 13:01:41 +02:00
|
|
|
return falseValue
|
2022-04-01 20:10:51 +02:00
|
|
|
}
|
2022-04-02 15:09:52 +02:00
|
|
|
|
|
|
|
func ConstrainBufferToAnchor(buf *ViewBuffer, anchor Anchor, width, height int) *ViewBuffer {
|
|
|
|
switch anchor {
|
|
|
|
default:
|
|
|
|
fallthrough
|
|
|
|
case AnchorTopLeft:
|
|
|
|
return buf.Sub(0, 0, width, height)
|
|
|
|
case AnchorTop:
|
|
|
|
return buf.Sub(buf.Width()/2-width/2, 0, width, height)
|
|
|
|
case AnchorTopRight:
|
|
|
|
return buf.Sub(buf.Width()-width, 0, width, height)
|
|
|
|
case AnchorLeft:
|
|
|
|
return buf.Sub(0, buf.Height()/2-height/2, width, height)
|
|
|
|
case AnchorCenter:
|
|
|
|
return buf.Sub(buf.Width()/2-width/2, buf.Height()/2-height/2, width, height)
|
|
|
|
case AnchorRight:
|
|
|
|
return buf.Sub(buf.Width()-width, buf.Height()/2-height/2, width, height)
|
|
|
|
case AnchorBottomLeft:
|
|
|
|
return buf.Sub(0, buf.Height()-height, width, height)
|
|
|
|
case AnchorBottom:
|
|
|
|
return buf.Sub(buf.Width()/2-width/2, buf.Height()-height, width, height)
|
|
|
|
case AnchorBottomRight:
|
|
|
|
return buf.Sub(buf.Width()-width, buf.Height()-height, width, height)
|
|
|
|
}
|
|
|
|
}
|
2022-05-03 17:59:34 +02:00
|
|
|
|
|
|
|
// CloseOnCtrlC returns a KeyPress handler which closes the screen when Ctrl-C is pressed.
|
|
|
|
// This is the default behavior for all new screens.
|
|
|
|
// CloseOnCtrlC is a shorthand for CloseOnKeyPressed(screen, tcell.KeyCtrlC)
|
|
|
|
func CloseOnCtrlC(screen *Screen) func(event *KeyEvent) (consumed bool) {
|
|
|
|
return CloseOnKeyPressed(screen, tcell.KeyCtrlC)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloseOnKeyPressed returns a KeyPress handler which closes the screen when the given key is pressed.
|
|
|
|
func CloseOnKeyPressed(screen *Screen, key tcell.Key) func(event *KeyEvent) (consumed bool) {
|
|
|
|
return func(event *KeyEvent) (consumed bool) {
|
|
|
|
if event.Key() == key {
|
|
|
|
screen.Stop()
|
2023-04-24 11:41:38 +02:00
|
|
|
return true
|
2022-05-03 17:59:34 +02:00
|
|
|
}
|
2023-04-24 14:15:33 +02:00
|
|
|
|
|
|
|
if screen.Root != nil {
|
|
|
|
return screen.Root.OnKeyPressed(event)
|
|
|
|
}
|
|
|
|
|
2023-04-24 11:41:38 +02:00
|
|
|
return false
|
2022-05-03 17:59:34 +02:00
|
|
|
}
|
|
|
|
}
|