2
0
mirror of https://github.com/rivo/tview.git synced 2024-11-17 03:26:09 +00:00

Add support for read only form items

When a form item is readonly, any input events or mouse events that
would cause its value to change are ignored.

Read only items are rendered when text color that matches the label
color to distinguish them from items that accept input.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2020-05-26 18:34:59 +01:00
parent 823f280c54
commit 78031b278f
3 changed files with 173 additions and 30 deletions

View File

@ -30,6 +30,12 @@ type Checkbox struct {
// The text color of the input area. // The text color of the input area.
fieldTextColor tcell.Color fieldTextColor tcell.Color
// The background color of the input area when readonly.
fieldBackgroundReadOnlyColor tcell.Color
// The text color of the input area when readonly.
fieldTextReadOnlyColor tcell.Color
// An optional function which is called when the user changes the checked // An optional function which is called when the user changes the checked
// state of this checkbox. // state of this checkbox.
changed func(checked bool) changed func(checked bool)
@ -42,6 +48,10 @@ type Checkbox struct {
// A callback function set by the Form class and called when the user leaves // A callback function set by the Form class and called when the user leaves
// this form item. // this form item.
finished func(tcell.Key) finished func(tcell.Key)
// If set the item does not respond to form input/mouse events
// that would change its contents
readonly bool
} }
// NewCheckbox returns a new input field. // NewCheckbox returns a new input field.
@ -51,6 +61,8 @@ func NewCheckbox() *Checkbox {
labelColor: Styles.SecondaryTextColor, labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor, fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor, fieldTextColor: Styles.PrimaryTextColor,
fieldTextReadOnlyColor: Styles.SecondaryTextColor,
fieldBackgroundReadOnlyColor: Styles.ContrastBackgroundColor,
} }
} }
@ -65,6 +77,13 @@ func (c *Checkbox) IsChecked() bool {
return c.checked return c.checked
} }
// SetReadOnly sets whether the item responds to input
// or mouse events that would change its contents
func (c *Checkbox) SetReadOnly(readonly bool) *Checkbox {
c.readonly = readonly
return c
}
// SetLabel sets the text to be displayed before the input area. // SetLabel sets the text to be displayed before the input area.
func (c *Checkbox) SetLabel(label string) *Checkbox { func (c *Checkbox) SetLabel(label string) *Checkbox {
c.label = label c.label = label
@ -101,6 +120,18 @@ func (c *Checkbox) SetFieldTextColor(color tcell.Color) *Checkbox {
return c return c
} }
// SetFieldBackgroundReadOnlyColor sets the background color of the input area when readonly.
func (c *Checkbox) SetFieldBackgroundReadOnlyColor(color tcell.Color) *Checkbox {
c.fieldBackgroundReadOnlyColor = color
return c
}
// SetFieldTextReadOnlyColor sets the text color of the input area when readonly.
func (c *Checkbox) SetFieldTextReadOnlyColor(color tcell.Color) *Checkbox {
c.fieldTextReadOnlyColor = color
return c
}
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.
func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
c.labelWidth = labelWidth c.labelWidth = labelWidth
@ -108,6 +139,8 @@ func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
c.backgroundColor = bgColor c.backgroundColor = bgColor
c.fieldTextColor = fieldTextColor c.fieldTextColor = fieldTextColor
c.fieldBackgroundColor = fieldBgColor c.fieldBackgroundColor = fieldBgColor
c.fieldTextReadOnlyColor = labelColor
c.fieldBackgroundReadOnlyColor = fieldBgColor
return c return c
} }
@ -168,7 +201,9 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
// Draw checkbox. // Draw checkbox.
fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor) fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor)
if c.focus.HasFocus() { if c.readonly {
fieldStyle = tcell.StyleDefault.Background(c.fieldBackgroundReadOnlyColor).Foreground(c.fieldTextReadOnlyColor)
} else if c.focus.HasFocus() {
fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(c.fieldBackgroundColor) fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(c.fieldBackgroundColor)
} }
checkedRune := 'X' checkedRune := 'X'
@ -184,6 +219,9 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
// Process key event. // Process key event.
switch key := event.Key(); key { switch key := event.Key(); key {
case tcell.KeyRune, tcell.KeyEnter: // Check. case tcell.KeyRune, tcell.KeyEnter: // Check.
if c.readonly {
break
}
if key == tcell.KeyRune && event.Rune() != ' ' { if key == tcell.KeyRune && event.Rune() != ' ' {
break break
} }
@ -214,10 +252,12 @@ func (c *Checkbox) MouseHandler() func(action MouseAction, event *tcell.EventMou
// Process mouse event. // Process mouse event.
if action == MouseLeftClick && y == rectY { if action == MouseLeftClick && y == rectY {
setFocus(c) setFocus(c)
if !c.readonly {
c.checked = !c.checked c.checked = !c.checked
if c.changed != nil { if c.changed != nil {
c.changed(c.checked) c.changed(c.checked)
} }
}
consumed = true consumed = true
} }

View File

@ -56,6 +56,12 @@ type DropDown struct {
// The text color of the input area. // The text color of the input area.
fieldTextColor tcell.Color fieldTextColor tcell.Color
// The background color of the input area when readonly.
fieldBackgroundReadOnlyColor tcell.Color
// The text color of the input area when readonly.
fieldTextReadOnlyColor tcell.Color
// The color for prefixes. // The color for prefixes.
prefixTextColor tcell.Color prefixTextColor tcell.Color
@ -81,6 +87,10 @@ type DropDown struct {
selected func(text string, index int) selected func(text string, index int)
dragging bool // Set to true when mouse dragging is in progress. dragging bool // Set to true when mouse dragging is in progress.
// If set the item does not respond to form input/mouse events
// that would change its contents
readonly bool
} }
// NewDropDown returns a new drop-down. // NewDropDown returns a new drop-down.
@ -101,6 +111,8 @@ func NewDropDown() *DropDown {
fieldBackgroundColor: Styles.ContrastBackgroundColor, fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor, fieldTextColor: Styles.PrimaryTextColor,
prefixTextColor: Styles.ContrastSecondaryTextColor, prefixTextColor: Styles.ContrastSecondaryTextColor,
fieldTextReadOnlyColor: Styles.SecondaryTextColor,
fieldBackgroundReadOnlyColor: Styles.ContrastBackgroundColor,
} }
d.focus = d d.focus = d
@ -158,6 +170,13 @@ func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix,
return d return d
} }
// SetReadOnly sets whether the item responds to input
// or mouse events that would change its contents
func (d *DropDown) SetReadOnly(readonly bool) *DropDown {
d.readonly = readonly
return d
}
// SetLabel sets the text to be displayed before the input area. // SetLabel sets the text to be displayed before the input area.
func (d *DropDown) SetLabel(label string) *DropDown { func (d *DropDown) SetLabel(label string) *DropDown {
d.label = label d.label = label
@ -202,6 +221,18 @@ func (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {
return d return d
} }
// SetFieldBackgroundColor sets the background color of the options area when readonly.
func (d *DropDown) SetFieldBackgroundReadOnlyColor(color tcell.Color) *DropDown {
d.fieldBackgroundReadOnlyColor = color
return d
}
// SetFieldTextRedaOnlyColor sets the text color of the options area when readonly.
func (d *DropDown) SetFieldTextReadOnlyColor(color tcell.Color) *DropDown {
d.fieldTextReadOnlyColor = color
return d
}
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.
func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
d.labelWidth = labelWidth d.labelWidth = labelWidth
@ -209,6 +240,8 @@ func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
d.backgroundColor = bgColor d.backgroundColor = bgColor
d.fieldTextColor = fieldTextColor d.fieldTextColor = fieldTextColor
d.fieldBackgroundColor = fieldBgColor d.fieldBackgroundColor = fieldBgColor
d.fieldTextReadOnlyColor = labelColor
d.fieldBackgroundReadOnlyColor = fieldBgColor
return d return d
} }
@ -340,7 +373,9 @@ func (d *DropDown) Draw(screen tcell.Screen) {
fieldWidth = rightLimit - x fieldWidth = rightLimit - x
} }
fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor) fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor)
if d.GetFocusable().HasFocus() && !d.open { if d.readonly {
fieldStyle = fieldStyle.Background(d.fieldBackgroundReadOnlyColor)
} else if d.GetFocusable().HasFocus() && !d.open {
fieldStyle = fieldStyle.Background(d.fieldTextColor) fieldStyle = fieldStyle.Background(d.fieldTextColor)
} }
for index := 0; index < fieldWidth; index++ { for index := 0; index < fieldWidth; index++ {
@ -360,14 +395,16 @@ func (d *DropDown) Draw(screen tcell.Screen) {
} }
} else { } else {
color := d.fieldTextColor color := d.fieldTextColor
if d.readonly {
color = d.fieldTextReadOnlyColor
} else if d.GetFocusable().HasFocus() && !d.open {
// Just show the current selection.
color = d.fieldBackgroundColor
}
text := d.noSelection text := d.noSelection
if d.currentOption >= 0 && d.currentOption < len(d.options) { if d.currentOption >= 0 && d.currentOption < len(d.options) {
text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix
} }
// Just show the current selection.
if d.GetFocusable().HasFocus() && !d.open {
color = d.fieldBackgroundColor
}
Print(screen, text, x, y, fieldWidth, AlignLeft, color) Print(screen, text, x, y, fieldWidth, AlignLeft, color)
} }
@ -511,7 +548,7 @@ func (d *DropDown) MouseHandler() func(action MouseAction, event *tcell.EventMou
x, y := event.Position() x, y := event.Position()
_, rectY, _, _ := d.GetInnerRect() _, rectY, _, _ := d.GetInnerRect()
inRect := y == rectY inRect := y == rectY
if !d.open && !inRect { if !d.open && !inRect || d.readonly {
return d.InRect(x, y), nil // No, and it's not expanded either. Ignore. return d.InRect(x, y), nil // No, and it's not expanded either. Ignore.
} }

View File

@ -51,6 +51,12 @@ type InputField struct {
// The text color of the input area. // The text color of the input area.
fieldTextColor tcell.Color fieldTextColor tcell.Color
// The background color of the input area when readonly.
fieldBackgroundReadOnlyColor tcell.Color
// The text color of the input area when readonly.
fieldTextReadOnlyColor tcell.Color
// The text color of the placeholder. // The text color of the placeholder.
placeholderTextColor tcell.Color placeholderTextColor tcell.Color
@ -96,6 +102,10 @@ type InputField struct {
fieldX int // The x-coordinate of the input field as determined during the last call to Draw(). fieldX int // The x-coordinate of the input field as determined during the last call to Draw().
offset int // The number of bytes of the text string skipped ahead while drawing. offset int // The number of bytes of the text string skipped ahead while drawing.
// If set the item does not respond to form input/mouse events
// that would change its contents
readonly bool
} }
// NewInputField returns a new input field. // NewInputField returns a new input field.
@ -106,6 +116,8 @@ func NewInputField() *InputField {
fieldBackgroundColor: Styles.ContrastBackgroundColor, fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor, fieldTextColor: Styles.PrimaryTextColor,
placeholderTextColor: Styles.ContrastSecondaryTextColor, placeholderTextColor: Styles.ContrastSecondaryTextColor,
fieldTextReadOnlyColor: Styles.SecondaryTextColor,
fieldBackgroundReadOnlyColor: Styles.ContrastBackgroundColor,
} }
} }
@ -124,6 +136,13 @@ func (i *InputField) GetText() string {
return i.text return i.text
} }
// SetReadOnly sets whether the item responds to input
// or mouse events that would change its contents
func (i *InputField) SetReadOnly(readonly bool) *InputField {
i.readonly = readonly
return i
}
// SetLabel sets the text to be displayed before the input area. // SetLabel sets the text to be displayed before the input area.
func (i *InputField) SetLabel(label string) *InputField { func (i *InputField) SetLabel(label string) *InputField {
i.label = label i.label = label
@ -172,6 +191,18 @@ func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
return i return i
} }
// SetFieldBackgroundColor sets the background color of the input area when readonly.
func (i *InputField) SetFieldBackgroundReadOnlyColor(color tcell.Color) *InputField {
i.fieldBackgroundReadOnlyColor = color
return i
}
// SetFieldTextReadOnlyColor sets the text color of the input area when readonly.
func (i *InputField) SetFieldTextReadOnlyColor(color tcell.Color) *InputField {
i.fieldTextReadOnlyColor = color
return i
}
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.
func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
i.labelWidth = labelWidth i.labelWidth = labelWidth
@ -179,6 +210,8 @@ func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fiel
i.backgroundColor = bgColor i.backgroundColor = bgColor
i.fieldTextColor = fieldTextColor i.fieldTextColor = fieldTextColor
i.fieldBackgroundColor = fieldBgColor i.fieldBackgroundColor = fieldBgColor
i.fieldTextReadOnlyColor = labelColor
i.fieldBackgroundReadOnlyColor = fieldBgColor
return i return i
} }
@ -334,7 +367,11 @@ func (i *InputField) Draw(screen tcell.Screen) {
if rightLimit-x < fieldWidth { if rightLimit-x < fieldWidth {
fieldWidth = rightLimit - x fieldWidth = rightLimit - x
} }
fieldStyle := tcell.StyleDefault.Background(i.fieldBackgroundColor) bgcolor := i.fieldBackgroundColor
if i.readonly {
bgcolor = i.fieldBackgroundReadOnlyColor
}
fieldStyle := tcell.StyleDefault.Background(bgcolor)
for index := 0; index < fieldWidth; index++ { for index := 0; index < fieldWidth; index++ {
screen.SetContent(x+index, y, ' ', nil, fieldStyle) screen.SetContent(x+index, y, ' ', nil, fieldStyle)
} }
@ -344,16 +381,24 @@ func (i *InputField) Draw(screen tcell.Screen) {
text := i.text text := i.text
if text == "" && i.placeholder != "" { if text == "" && i.placeholder != "" {
// Draw placeholder text. // Draw placeholder text.
Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, i.placeholderTextColor) color := i.placeholderTextColor
if i.readonly {
color = i.fieldTextReadOnlyColor
}
Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, color)
i.offset = 0 i.offset = 0
} else { } else {
// Draw entered text. // Draw entered text.
color := i.fieldTextColor
if i.readonly {
color = i.fieldTextReadOnlyColor
}
if i.maskCharacter > 0 { if i.maskCharacter > 0 {
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text)) text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
} }
if fieldWidth >= stringWidth(text) { if fieldWidth >= stringWidth(text) {
// We have enough space for the full text. // We have enough space for the full text.
Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, i.fieldTextColor) Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, color)
i.offset = 0 i.offset = 0
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool { iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
if textPos >= i.cursorPos { if textPos >= i.cursorPos {
@ -391,7 +436,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
} }
return false return false
}) })
Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, i.fieldTextColor) Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, color)
} }
} }
@ -508,27 +553,45 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
case 'f': // Move word right. case 'f': // Move word right.
moveWordRight() moveWordRight()
default: default:
if i.readonly {
break
}
if !add(event.Rune()) { if !add(event.Rune()) {
return return
} }
} }
} else { } else {
if i.readonly {
break
}
// Other keys are simply accepted as regular characters. // Other keys are simply accepted as regular characters.
if !add(event.Rune()) { if !add(event.Rune()) {
return return
} }
} }
case tcell.KeyCtrlU: // Delete all. case tcell.KeyCtrlU: // Delete all.
if i.readonly {
break
}
i.text = "" i.text = ""
i.cursorPos = 0 i.cursorPos = 0
case tcell.KeyCtrlK: // Delete until the end of the line. case tcell.KeyCtrlK: // Delete until the end of the line.
if i.readonly {
break
}
i.text = i.text[:i.cursorPos] i.text = i.text[:i.cursorPos]
case tcell.KeyCtrlW: // Delete last word. case tcell.KeyCtrlW: // Delete last word.
if i.readonly {
break
}
lastWord := regexp.MustCompile(`\S+\s*$`) lastWord := regexp.MustCompile(`\S+\s*$`)
newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:] newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
i.cursorPos -= len(i.text) - len(newText) i.cursorPos -= len(i.text) - len(newText)
i.text = newText i.text = newText
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor. case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
if i.readonly {
break
}
iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool { iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = i.text[:textPos] + i.text[textPos+textWidth:] i.text = i.text[:textPos] + i.text[textPos+textWidth:]
i.cursorPos -= textWidth i.cursorPos -= textWidth
@ -538,6 +601,9 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
i.offset = 0 i.offset = 0
} }
case tcell.KeyDelete: // Delete character after the cursor. case tcell.KeyDelete: // Delete character after the cursor.
if i.readonly {
break
}
iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool { iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:] i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
return true return true