diff --git a/demos/form/main.go b/demos/form/main.go index 7939b0d..ef24cdb 100644 --- a/demos/form/main.go +++ b/demos/form/main.go @@ -12,6 +12,7 @@ func main() { AddInputField("First name", "", 20, nil, nil). AddInputField("Last name", "", 20, nil, nil). AddCheckbox("Age 18+", false, nil). + AddPasswordField("Password", "", 10, '*', nil). AddButton("Save", nil). AddButton("Quit", func() { app.Stop() diff --git a/demos/form/screenshot.png b/demos/form/screenshot.png index a32b315..a7c0379 100644 Binary files a/demos/form/screenshot.png and b/demos/form/screenshot.png differ diff --git a/form.go b/form.go index a3e30d6..ab8bd6e 100644 --- a/form.go +++ b/form.go @@ -138,7 +138,27 @@ func (f *Form) AddInputField(label, value string, fieldLength int, accept func(t SetLabel(label). SetText(value). SetFieldLength(fieldLength). - SetAcceptanceFunc(accept)) + SetAcceptanceFunc(accept). + SetChangedFunc(changed)) + return f +} + +// AddPasswordField adds a password field to the form. This is similar to an +// input field except that the user's input not shown. Instead, a "mask" +// character is displayed. The password field has a label, an optional initial +// value, a field length (a value of 0 extends it as far as possible), and an +// (optional) callback function which is invoked when the input field's text has +// changed. +func (f *Form) AddPasswordField(label, value string, fieldLength int, mask rune, changed func(text string)) *Form { + if mask == 0 { + mask = '*' + } + f.items = append(f.items, NewInputField(). + SetLabel(label). + SetText(value). + SetFieldLength(fieldLength). + SetMaskCharacter(mask). + SetChangedFunc(changed)) return f } diff --git a/inputfield.go b/inputfield.go index 2403973..12a4e5e 100644 --- a/inputfield.go +++ b/inputfield.go @@ -3,6 +3,8 @@ package tview import ( "math" "regexp" + "strings" + "unicode/utf8" "github.com/gdamore/tcell" runewidth "github.com/mattn/go-runewidth" @@ -11,6 +13,9 @@ import ( // InputField is a one-line box (three lines if there is a title) where the // user can enter text. // +// Use SetMaskCharacter() to hide input from onlookers (e.g. for password +// input). +// // See https://github.com/rivo/tview/wiki/InputField for an example. type InputField struct { *Box @@ -34,6 +39,10 @@ type InputField struct { // possible. fieldLength int + // A character to mask entered text (useful for password fields). A value of 0 + // disables masking. + maskCharacter rune + // An optional function which may reject the last character that was entered. accept func(text string, ch rune) bool @@ -116,6 +125,13 @@ func (i *InputField) SetFieldLength(length int) *InputField { return i } +// SetMaskCharacter sets a character that masks user input on a screen. A value +// of 0 disables masking. +func (i *InputField) SetMaskCharacter(mask rune) *InputField { + i.maskCharacter = mask + return i +} + // SetAcceptanceFunc sets a handler which may reject the last character that was // entered (by returning false). // @@ -180,11 +196,15 @@ func (i *InputField) Draw(screen tcell.Screen) { } // Draw entered text. + text := i.text + if i.maskCharacter > 0 { + text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text)) + } fieldLength-- // We need one cell for the cursor. if fieldLength < runewidth.StringWidth(i.text) { - Print(screen, i.text, x, y, fieldLength, AlignRight, i.fieldTextColor) + Print(screen, text, x, y, fieldLength, AlignRight, i.fieldTextColor) } else { - Print(screen, i.text, x, y, fieldLength, AlignLeft, i.fieldTextColor) + Print(screen, text, x, y, fieldLength, AlignLeft, i.fieldTextColor) } // Set cursor.