258 lines
4.7 KiB
Go
258 lines
4.7 KiB
Go
package input
|
|
|
|
import "unicode"
|
|
import "git.tebibyte.media/sashakoshka/stone"
|
|
|
|
var selectedInput *Input
|
|
|
|
func SelectNone () {
|
|
selectedInput = nil
|
|
}
|
|
|
|
type Input struct {
|
|
cursor int
|
|
text []rune
|
|
X, Y, Width int
|
|
Obscure bool
|
|
OnDone func ()
|
|
OnCancel func ()
|
|
}
|
|
|
|
func (input *Input) HandleButton (button stone.Button, control, shift, alt bool) {
|
|
switch button {
|
|
case stone.KeyLeft:
|
|
if control {
|
|
input.cursor -= input.distanceToPreviousWordBound()
|
|
} else if alt {
|
|
input.cursorToBeginning()
|
|
} else {
|
|
input.cursor --
|
|
}
|
|
input.constrainCursor()
|
|
|
|
case stone.KeyRight:
|
|
if control {
|
|
input.cursor += input.distanceToNextWordBound()
|
|
} else if alt {
|
|
input.cursorToEnd()
|
|
} else {
|
|
input.cursor ++
|
|
}
|
|
input.constrainCursor()
|
|
|
|
case stone.KeyHome:
|
|
input.cursorToBeginning()
|
|
input.constrainCursor()
|
|
|
|
case stone.KeyEnd:
|
|
input.cursorToEnd()
|
|
input.constrainCursor()
|
|
|
|
case stone.KeyBackspace:
|
|
if control {
|
|
input.backspace(input.distanceToPreviousWordBound())
|
|
} else if alt {
|
|
input.backspace(input.cursor)
|
|
} else {
|
|
input.backspace(1)
|
|
}
|
|
input.constrainCursor()
|
|
|
|
case stone.KeyDelete:
|
|
if control {
|
|
input.delete(input.distanceToNextWordBound())
|
|
} else if alt {
|
|
input.delete(len(input.text))
|
|
} else {
|
|
input.delete(1)
|
|
}
|
|
input.constrainCursor()
|
|
|
|
case stone.KeyEnter:
|
|
SelectNone()
|
|
if input.OnDone != nil {
|
|
input.OnDone()
|
|
}
|
|
|
|
case stone.KeyEscape:
|
|
SelectNone()
|
|
if input.OnCancel != nil {
|
|
input.OnCancel()
|
|
}
|
|
|
|
default:
|
|
if button.Printable() {
|
|
input.text = append(input.text, 0)
|
|
copy (
|
|
input.text[input.cursor + 1:],
|
|
input.text[input.cursor:])
|
|
input.text[input.cursor] = rune(button)
|
|
input.cursor ++
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (input *Input) Draw (buffer stone.Buffer) {
|
|
buffer.SetRune(input.X, input.Y, '[')
|
|
buffer.SetRune(input.X + 1, input.Y, 0)
|
|
buffer.SetRune(input.X + input.Width - 2, input.Y, 0)
|
|
buffer.SetRune(input.X + input.Width - 1, input.Y, ']')
|
|
|
|
actualWidth := input.Width - 4
|
|
scroll := 0
|
|
|
|
x := input.X + 2
|
|
for index := 0; index < actualWidth; index ++ {
|
|
character := input.charAtSafe(index + scroll)
|
|
|
|
if input.Selected() && index == input.cursor {
|
|
buffer.SetStyle(x, input.Y, stone.StyleUnderline)
|
|
} else {
|
|
buffer.SetStyle(x, input.Y, stone.StyleNormal)
|
|
}
|
|
|
|
if input.Obscure && character > 0 {
|
|
buffer.SetRune(x, input.Y, '*')
|
|
} else {
|
|
buffer.SetRune(x, input.Y, character)
|
|
}
|
|
x ++
|
|
}
|
|
}
|
|
|
|
func (input *Input) charAtSafe (index int) (character rune) {
|
|
if index < 0 { return }
|
|
if index >= len(input.text) { return }
|
|
character = input.text[index]
|
|
return
|
|
}
|
|
|
|
func (input *Input) constrainCursor () (constrained bool) {
|
|
if input.cursor < 0 {
|
|
input.cursorToBeginning()
|
|
constrained = true
|
|
} else if input.cursor > len(input.text) {
|
|
input.cursorToEnd()
|
|
constrained = true
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (input *Input) distanceToPreviousWordBound () (distance int) {
|
|
index := input.cursor
|
|
index --
|
|
distance ++
|
|
|
|
for {
|
|
if index < 0 { break }
|
|
|
|
character := input.text[index]
|
|
isWordChar :=
|
|
unicode.IsLetter(character) ||
|
|
unicode.IsDigit(character)
|
|
if !isWordChar {
|
|
distance --
|
|
break
|
|
}
|
|
|
|
distance ++
|
|
index --
|
|
}
|
|
|
|
if distance < 1 {
|
|
distance ++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (input *Input) distanceToNextWordBound () (distance int) {
|
|
index := input.cursor
|
|
|
|
for {
|
|
if index >= len(input.text) { break }
|
|
|
|
character := input.text[index]
|
|
isWordChar :=
|
|
unicode.IsLetter(character) ||
|
|
unicode.IsDigit(character)
|
|
if !isWordChar {
|
|
break
|
|
}
|
|
|
|
distance ++
|
|
index ++
|
|
}
|
|
|
|
if distance < 1 {
|
|
distance ++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (input *Input) backspace (amount int) {
|
|
if input.cursor - amount < 0 {
|
|
amount = input.cursor
|
|
}
|
|
|
|
copy (
|
|
input.text[input.cursor - amount:],
|
|
input.text[input.cursor:])
|
|
input.text = input.text[:len(input.text) - amount]
|
|
input.cursor -= amount
|
|
}
|
|
|
|
func (input *Input) delete (amount int) {
|
|
if input.cursor + amount > len(input.text) {
|
|
amount = len(input.text) - input.cursor
|
|
}
|
|
|
|
// TODO
|
|
copy (
|
|
input.text[input.cursor:],
|
|
input.text[input.cursor + amount:])
|
|
input.text = input.text[:len(input.text) - amount]
|
|
}
|
|
|
|
func (input *Input) cursorToBeginning () {
|
|
input.cursor = 0
|
|
}
|
|
|
|
func (input *Input) cursorToEnd () {
|
|
input.cursor = len(input.text)
|
|
}
|
|
|
|
func (input *Input) Reset () {
|
|
input.SetText("")
|
|
}
|
|
|
|
func (input *Input) Text () (text string) {
|
|
text = string(input.text)
|
|
return
|
|
}
|
|
|
|
func (input *Input) SetText (text string) {
|
|
input.text = []rune(text)
|
|
input.cursorToEnd()
|
|
return
|
|
}
|
|
|
|
func (input *Input) Selected () (selected bool) {
|
|
selected = selectedInput == input
|
|
return
|
|
}
|
|
|
|
func (input *Input) Select () () {
|
|
selectedInput = input
|
|
return
|
|
}
|
|
|
|
func Selected () (selected *Input) {
|
|
selected = selectedInput
|
|
return
|
|
}
|