package tui import ( "strings" "git.milar.in/milarin/gmath" "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" ) // 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 func WriteString(b *ViewBuffer, str string, style Style, x, y int) (width int) { dx := x for _, r := range str { if dx >= b.Width() { return } b.Set(dx, y, Rune{r, style}) dx += runeWidth(r) } return dx - x } // 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 func WriteMultiLineString(b *ViewBuffer, str string, style Style, x, y int) (maxLineWidth, lineCount int) { lines := strings.Split(str, "\n") for dy, line := range lines { if dy >= b.Height() { return } lineWidth := WriteString(b, line, style, x, y+dy) maxLineWidth = gmath.Max(maxLineWidth, lineWidth) } 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) maxLineWidth = gmath.Max(maxLineWidth, lineWidth) } return maxLineWidth, len(lines) } func runeWidth(r rune) int { return runewidth.RuneWidth(r) //fmt.Println(r, width.LookupRune(r).Kind()) // switch width.LookupRune(r).Kind() { // case width.EastAsianFullwidth: // fallthrough // case width.EastAsianWide: // return 2 // default: // return 1 // } } func iff[T any](condition bool, trueValue, falseValue T) T { if condition { return trueValue } return falseValue } 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) } } // 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() return true } if screen.Root != nil { return screen.Root.OnKeyPressed(event) } return false } }