157 lines
4.2 KiB
Go
157 lines
4.2 KiB
Go
// Standard components
|
|
|
|
package ui
|
|
|
|
import "unicode"
|
|
|
|
// Status bar component. This component displays text on the left and right of it's
|
|
// allocated space.
|
|
type StatusBar struct {
|
|
Left string // Left aligned string
|
|
Right string // Right aligned string
|
|
}
|
|
|
|
// Minimum dimensions
|
|
func (sbar *StatusBar) Remeasure(w, h int) (int, int) {
|
|
return w, 1
|
|
}
|
|
|
|
// Status bar redraw
|
|
func (sbar *StatusBar) Redraw(context *DrawContext) {
|
|
context.SetFgAttr(AttrReverse)
|
|
context.SetBgAttr(AttrReverse)
|
|
|
|
context.HorizRule(0, ' ')
|
|
context.Print(0, 0, sbar.Left)
|
|
context.PrintRight(context.W, 0, sbar.Right)
|
|
}
|
|
|
|
|
|
// A single-text entry component.
|
|
type TextEntry struct {
|
|
Prompt string
|
|
|
|
value string
|
|
cursorOffset int
|
|
displayOffset int
|
|
}
|
|
|
|
func (te *TextEntry) Remeasure(w, h int) (int, int) {
|
|
return w, 1
|
|
}
|
|
|
|
func (te *TextEntry) Redraw(context *DrawContext) {
|
|
context.HorizRule(0, ' ')
|
|
valueOffsetX := 0
|
|
displayOffsetX := te.calculateDisplayOffset(context.W)
|
|
|
|
if te.Prompt != "" {
|
|
context.SetFgAttr(ColorDefault | AttrBold)
|
|
context.Print(0, 0, te.Prompt)
|
|
context.SetFgAttr(ColorDefault)
|
|
|
|
valueOffsetX = len(te.Prompt)
|
|
}
|
|
|
|
context.Print(valueOffsetX, 0, te.value[displayOffsetX:intMin(displayOffsetX + context.W,len(te.value))])
|
|
context.SetCursorPosition(te.cursorOffset + valueOffsetX - displayOffsetX, 0)
|
|
|
|
//context.Print(0, 0, fmt.Sprintf("%d,%d", te.cursorOffset, displayOffsetX))
|
|
}
|
|
|
|
func (te *TextEntry) calculateDisplayOffset(displayWidth int) int {
|
|
if te.Prompt != "" {
|
|
displayWidth -= len(te.Prompt)
|
|
}
|
|
virtualCursorOffset := te.cursorOffset - te.displayOffset
|
|
|
|
if virtualCursorOffset >= displayWidth {
|
|
te.displayOffset = te.cursorOffset - displayWidth + 10
|
|
} else if (virtualCursorOffset < 0) {
|
|
te.displayOffset = intMax(te.cursorOffset - displayWidth + 1, 0)
|
|
}
|
|
|
|
return te.displayOffset
|
|
}
|
|
|
|
func (te *TextEntry) KeyPressed(key rune, mod int) {
|
|
if (key >= ' ') && (key <= '~') {
|
|
te.insertRune(key)
|
|
} else if (key == KeyArrowLeft) {
|
|
te.moveCursorBy(-1)
|
|
} else if (key == KeyArrowRight) {
|
|
te.moveCursorBy(1)
|
|
} else if (key == KeyHome) {
|
|
te.moveCursorTo(0)
|
|
} else if (key == KeyEnd) {
|
|
te.moveCursorTo(len(te.value))
|
|
} else if (key == KeyBackspace) || (key == KeyBackspace2) {
|
|
if (mod & ModKeyAlt != 0) {
|
|
te.backspaceWhile(unicode.IsSpace)
|
|
te.backspaceWhile(func(r rune) bool { return !unicode.IsSpace(r) })
|
|
} else {
|
|
te.backspace()
|
|
}
|
|
} else if (key == KeyCtrlK) {
|
|
te.killLine()
|
|
} else if (key == KeyDelete) {
|
|
te.removeCharAtPos(te.cursorOffset)
|
|
} else if (key == KeyEnter) {
|
|
panic("Entered text: '" + te.value + "'")
|
|
}
|
|
}
|
|
|
|
// Backspace
|
|
func (te *TextEntry) backspace() {
|
|
te.removeCharAtPos(te.cursorOffset - 1)
|
|
te.moveCursorBy(-1)
|
|
}
|
|
|
|
// Backspace while the character underneith the cursor matches the guard
|
|
func (te *TextEntry) backspaceWhile(guard func(r rune) bool) {
|
|
for (te.cursorOffset > 0) {
|
|
ch := rune(te.value[te.cursorOffset - 1])
|
|
if guard(ch) {
|
|
te.backspace()
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Kill the line. If the cursor is at the end of the line, kill to the start.
|
|
// Otherwise, trim the line.
|
|
func (te *TextEntry) killLine() {
|
|
if (te.cursorOffset < len(te.value)) {
|
|
te.value = te.value[:te.cursorOffset]
|
|
} else {
|
|
te.value = ""
|
|
te.cursorOffset = 0
|
|
}
|
|
}
|
|
|
|
// Inserts a rune at the cursor position
|
|
func (te *TextEntry) insertRune(key rune) {
|
|
if (te.cursorOffset >= len(te.value)) {
|
|
te.value += string(key)
|
|
} else {
|
|
te.value = te.value[:te.cursorOffset] + string(key) + te.value[te.cursorOffset:]
|
|
}
|
|
te.moveCursorBy(1)
|
|
}
|
|
|
|
// Remove the character at a specific position
|
|
func (te *TextEntry) removeCharAtPos(pos int) {
|
|
if (pos >= 0) && (pos < len(te.value)) {
|
|
te.value = te.value[:pos] + te.value[pos+1:]
|
|
}
|
|
}
|
|
|
|
// Move the cursor
|
|
func (te *TextEntry) moveCursorBy(byX int) {
|
|
te.moveCursorTo(te.cursorOffset + byX)
|
|
}
|
|
|
|
func (te *TextEntry) moveCursorTo(toX int) {
|
|
te.cursorOffset = intMinMax(toX, 0, len(te.value))
|
|
} |