aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

textinput.go (4159B)


      1 package ui
      2 
      3 import (
      4 	"github.com/gdamore/tcell"
      5 	"github.com/mattn/go-runewidth"
      6 )
      7 
      8 // TODO: Attach history and tab completion providers
      9 // TODO: scrolling
     10 
     11 type TextInput struct {
     12 	Invalidatable
     13 	cells    int
     14 	ctx      *Context
     15 	focus    bool
     16 	index    int
     17 	password bool
     18 	prompt   string
     19 	scroll   int
     20 	text     []rune
     21 	change   []func(ti *TextInput)
     22 }
     23 
     24 // Creates a new TextInput. TextInputs will render a "textbox" in the entire
     25 // context they're given, and process keypresses to build a string from user
     26 // input.
     27 func NewTextInput(text string) *TextInput {
     28 	return &TextInput{
     29 		cells: -1,
     30 		text:  []rune(text),
     31 		index: len([]rune(text)),
     32 	}
     33 }
     34 
     35 func (ti *TextInput) Password(password bool) *TextInput {
     36 	ti.password = password
     37 	return ti
     38 }
     39 
     40 func (ti *TextInput) Prompt(prompt string) *TextInput {
     41 	ti.prompt = prompt
     42 	return ti
     43 }
     44 
     45 func (ti *TextInput) String() string {
     46 	return string(ti.text)
     47 }
     48 
     49 func (ti *TextInput) Set(value string) {
     50 	ti.text = []rune(value)
     51 	ti.index = len(ti.text)
     52 }
     53 
     54 func (ti *TextInput) Invalidate() {
     55 	ti.DoInvalidate(ti)
     56 }
     57 
     58 func (ti *TextInput) Draw(ctx *Context) {
     59 	scroll := ti.scroll
     60 	if !ti.focus {
     61 		scroll = 0
     62 	} else {
     63 		ti.ensureScroll()
     64 	}
     65 	ti.ctx = ctx // gross
     66 	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
     67 
     68 	text := string(ti.text[scroll:])
     69 	sindex := ti.index - scroll
     70 	if ti.password {
     71 		x := ctx.Printf(0, 0, tcell.StyleDefault, "%s", ti.prompt)
     72 		cells := runewidth.StringWidth(string(text))
     73 		ctx.Fill(x, 0, cells, 1, '*', tcell.StyleDefault)
     74 	} else {
     75 		ctx.Printf(0, 0, tcell.StyleDefault, "%s%s", ti.prompt, text)
     76 	}
     77 	cells := runewidth.StringWidth(text[:sindex] + ti.prompt)
     78 	if ti.focus {
     79 		ctx.SetCursor(cells, 0)
     80 	}
     81 }
     82 
     83 func (ti *TextInput) Focus(focus bool) {
     84 	ti.focus = focus
     85 	if focus && ti.ctx != nil {
     86 		cells := runewidth.StringWidth(string(ti.text[:ti.index]))
     87 		ti.ctx.SetCursor(cells+1, 0)
     88 	} else if !focus && ti.ctx != nil {
     89 		ti.ctx.HideCursor()
     90 	}
     91 }
     92 
     93 func (ti *TextInput) ensureScroll() {
     94 	if ti.ctx == nil {
     95 		return
     96 	}
     97 	// God why am I this lazy
     98 	for ti.index-ti.scroll >= ti.ctx.Width() {
     99 		ti.scroll++
    100 	}
    101 	for ti.index-ti.scroll < 0 {
    102 		ti.scroll--
    103 	}
    104 }
    105 
    106 func (ti *TextInput) insert(ch rune) {
    107 	left := ti.text[:ti.index]
    108 	right := ti.text[ti.index:]
    109 	ti.text = append(left, append([]rune{ch}, right...)...)
    110 	ti.index++
    111 	ti.ensureScroll()
    112 	ti.Invalidate()
    113 	ti.onChange()
    114 }
    115 
    116 func (ti *TextInput) deleteWord() {
    117 	// TODO: Break on any of / " '
    118 	if len(ti.text) == 0 {
    119 		return
    120 	}
    121 	i := ti.index - 1
    122 	if ti.text[i] == ' ' {
    123 		i--
    124 	}
    125 	for ; i >= 0; i-- {
    126 		if ti.text[i] == ' ' {
    127 			break
    128 		}
    129 	}
    130 	ti.text = append(ti.text[:i+1], ti.text[ti.index:]...)
    131 	ti.index = i + 1
    132 	ti.ensureScroll()
    133 	ti.Invalidate()
    134 	ti.onChange()
    135 }
    136 
    137 func (ti *TextInput) deleteChar() {
    138 	if len(ti.text) > 0 && ti.index != len(ti.text) {
    139 		ti.text = append(ti.text[:ti.index], ti.text[ti.index+1:]...)
    140 		ti.ensureScroll()
    141 		ti.Invalidate()
    142 		ti.onChange()
    143 	}
    144 }
    145 
    146 func (ti *TextInput) backspace() {
    147 	if len(ti.text) > 0 && ti.index != 0 {
    148 		ti.text = append(ti.text[:ti.index-1], ti.text[ti.index:]...)
    149 		ti.index--
    150 		ti.ensureScroll()
    151 		ti.Invalidate()
    152 		ti.onChange()
    153 	}
    154 }
    155 
    156 func (ti *TextInput) onChange() {
    157 	for _, change := range ti.change {
    158 		change(ti)
    159 	}
    160 }
    161 
    162 func (ti *TextInput) OnChange(onChange func(ti *TextInput)) {
    163 	ti.change = append(ti.change, onChange)
    164 }
    165 
    166 func (ti *TextInput) Event(event tcell.Event) bool {
    167 	switch event := event.(type) {
    168 	case *tcell.EventKey:
    169 		switch event.Key() {
    170 		case tcell.KeyBackspace, tcell.KeyBackspace2:
    171 			ti.backspace()
    172 		case tcell.KeyCtrlD, tcell.KeyDelete:
    173 			ti.deleteChar()
    174 		case tcell.KeyCtrlB, tcell.KeyLeft:
    175 			if ti.index > 0 {
    176 				ti.index--
    177 				ti.ensureScroll()
    178 				ti.Invalidate()
    179 			}
    180 		case tcell.KeyCtrlF, tcell.KeyRight:
    181 			if ti.index < len(ti.text) {
    182 				ti.index++
    183 				ti.ensureScroll()
    184 				ti.Invalidate()
    185 			}
    186 		case tcell.KeyCtrlA, tcell.KeyHome:
    187 			ti.index = 0
    188 			ti.ensureScroll()
    189 			ti.Invalidate()
    190 		case tcell.KeyCtrlE, tcell.KeyEnd:
    191 			ti.index = len(ti.text)
    192 			ti.ensureScroll()
    193 			ti.Invalidate()
    194 		case tcell.KeyCtrlW:
    195 			ti.deleteWord()
    196 		case tcell.KeyRune:
    197 			ti.insert(event.Rune())
    198 		}
    199 	}
    200 	return true
    201 }