Form elements can now also be disabled. Resolves #192

pull/836/head
Oliver 1 year ago
parent cc10b288e3
commit 47b3275db4

@ -10,6 +10,9 @@ import (
type Button struct {
*Box
// If set to true, the button cannot be activated.
disabled bool
// The text to be displayed inside the button.
text string
@ -19,6 +22,9 @@ type Button struct {
// The button's style (when activated).
activatedStyle tcell.Style
// The button's style (when disabled).
disabledStyle tcell.Style
// An optional function which is called when the button was selected.
selected func()
@ -37,6 +43,7 @@ func NewButton(label string) *Button {
text: label,
style: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
activatedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.InverseTextColor),
disabledStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor),
}
}
@ -83,6 +90,27 @@ func (b *Button) SetActivatedStyle(style tcell.Style) *Button {
return b
}
// SetDisabledStyle sets the style of the button used when it is disabled.
func (b *Button) SetDisabledStyle(style tcell.Style) *Button {
b.disabledStyle = style
return b
}
// SetDisabled sets whether or not the button is disabled. Disabled buttons
// cannot be activated.
//
// If the button is part of a form, you should set focus to the form itself
// after calling this function to set focus to the next non-disabled form item.
func (b *Button) SetDisabled(disabled bool) *Button {
b.disabled = disabled
return b
}
// IsDisabled returns whether or not the button is disabled.
func (b *Button) IsDisabled() bool {
return b.disabled
}
// SetSelectedFunc sets a handler which is called when the button was selected.
func (b *Button) SetSelectedFunc(handler func()) *Button {
b.selected = handler
@ -105,8 +133,11 @@ func (b *Button) SetExitFunc(handler func(key tcell.Key)) *Button {
func (b *Button) Draw(screen tcell.Screen) {
// Draw the box.
style := b.style
if b.disabled {
style = b.disabledStyle
}
_, backgroundColor, _ := style.Decompose()
if b.HasFocus() {
if b.HasFocus() && !b.disabled {
style = b.activatedStyle
_, backgroundColor, _ = style.Decompose()
@ -131,6 +162,10 @@ func (b *Button) Draw(screen tcell.Screen) {
// InputHandler returns the handler for this primitive.
func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return b.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if b.disabled {
return
}
// Process key event.
switch key := event.Key(); key {
case tcell.KeyEnter: // Selected.
@ -148,6 +183,10 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim
// MouseHandler returns the mouse handler for this primitive.
func (b *Button) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if b.disabled {
return false, nil
}
if !b.InRect(event.Position()) {
return false, nil
}

@ -14,6 +14,9 @@ import (
type Checkbox struct {
*Box
// Whether or not this checkbox is disabled/read-only.
disabled bool
// Whether or not this box is checked.
checked bool
@ -135,6 +138,15 @@ func (c *Checkbox) GetFieldHeight() int {
return 1
}
// SetDisabled sets whether or not the item is disabled / read-only.
func (c *Checkbox) SetDisabled(disabled bool) FormItem {
c.disabled = disabled
if c.finished != nil {
c.finished(-1)
}
return c
}
// SetChangedFunc sets a handler which is called when the checked state of this
// checkbox was changed by the user. The handler function receives the new
// state.
@ -161,6 +173,18 @@ func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return c
}
// Focus is called when this primitive receives focus.
func (c *Checkbox) Focus(delegate func(p Primitive)) {
// If we're part of a form and this item is disabled, there's nothing the
// user can do here so we're finished.
if c.finished != nil && c.disabled {
c.finished(-1)
return
}
c.Box.Focus(delegate)
}
// Draw draws this primitive onto the screen.
func (c *Checkbox) Draw(screen tcell.Screen) {
c.Box.DrawForSubclass(screen, c)
@ -186,9 +210,13 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
}
// Draw checkbox.
fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor)
fieldBackgroundColor := c.fieldBackgroundColor
if c.disabled {
fieldBackgroundColor = c.backgroundColor
}
fieldStyle := tcell.StyleDefault.Background(fieldBackgroundColor).Foreground(c.fieldTextColor)
if c.HasFocus() {
fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(c.fieldBackgroundColor)
fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(fieldBackgroundColor)
}
checkboxWidth := uniseg.StringWidth(c.checkedString)
checkedString := c.checkedString
@ -201,6 +229,10 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
// InputHandler returns the handler for this primitive.
func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if c.disabled {
return
}
// Process key event.
switch key := event.Key(); key {
case tcell.KeyRune, tcell.KeyEnter: // Check.
@ -225,6 +257,10 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
// MouseHandler returns the mouse handler for this primitive.
func (c *Checkbox) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return c.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if c.disabled {
return false, nil
}
x, y := event.Position()
_, rectY, _, _ := c.GetInnerRect()
if !c.InRect(x, y) {

@ -20,6 +20,9 @@ type dropDownOption struct {
type DropDown struct {
*Box
// Whether or not this drop-down is disabled/read-only.
disabled bool
// The options from which the user can choose.
options []*dropDownOption
@ -249,6 +252,15 @@ func (d *DropDown) GetFieldHeight() int {
return 1
}
// SetDisabled sets whether or not the item is disabled / read-only.
func (d *DropDown) SetDisabled(disabled bool) FormItem {
d.disabled = disabled
if d.finished != nil {
d.finished(-1)
}
return d
}
// AddOption adds a new selectable option to this drop-down. The "selected"
// callback is called when this option was selected. It may be nil.
func (d *DropDown) AddOption(text string, selected func()) *DropDown {
@ -371,6 +383,9 @@ func (d *DropDown) Draw(screen tcell.Screen) {
if d.HasFocus() && !d.open {
fieldStyle = fieldStyle.Background(d.fieldTextColor)
}
if d.disabled {
fieldStyle = fieldStyle.Background(d.backgroundColor)
}
for index := 0; index < fieldWidth; index++ {
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
}
@ -393,7 +408,7 @@ func (d *DropDown) Draw(screen tcell.Screen) {
text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix
}
// Just show the current selection.
if d.HasFocus() && !d.open {
if d.HasFocus() && !d.open && !d.disabled {
color = d.fieldBackgroundColor
}
Print(screen, text, x, y, fieldWidth, AlignLeft, color)
@ -432,6 +447,10 @@ func (d *DropDown) Draw(screen tcell.Screen) {
// InputHandler returns the handler for this primitive.
func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if d.disabled {
return
}
// If the list has focus, let it process its own key events.
if d.list.HasFocus() {
if handler := d.list.InputHandler(); handler != nil {
@ -534,6 +553,13 @@ func (d *DropDown) closeList(setFocus func(Primitive)) {
// Focus is called by the application when the primitive receives focus.
func (d *DropDown) Focus(delegate func(p Primitive)) {
// If we're part of a form and this item is disabled, there's nothing the
// user can do here so we're finished.
if d.finished != nil && d.disabled {
d.finished(-1)
return
}
if d.open {
delegate(d.list)
} else {
@ -552,6 +578,10 @@ func (d *DropDown) HasFocus() bool {
// MouseHandler returns the mouse handler for this primitive.
func (d *DropDown) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return d.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if d.disabled {
return false, nil
}
// Was the mouse event in the drop-down box itself (or on its label)?
x, y := event.Position()
rectX, rectY, rectWidth, _ := d.GetInnerRect()

@ -45,6 +45,10 @@ type FormItem interface {
// value, indicating that the action for the last known key should be
// repeated.
SetFinishedFunc(handler func(key tcell.Key)) FormItem
// SetDisabled sets whether or not the item is disabled / read-only. A form
// must have at least one item that is not disabled.
SetDisabled(disabled bool) FormItem
}
// Form allows you to combine multiple one-line form elements into a vertical
@ -92,6 +96,9 @@ type Form struct {
// The style of the buttons when they are focused.
buttonActivatedStyle tcell.Style
// The style of the buttons when they are disabled.
buttonDisabledStyle tcell.Style
// The last (valid) key that wsa sent to a "finished" handler or -1 if no
// such key is known yet.
lastFinishedKey tcell.Key
@ -662,12 +669,6 @@ func (f *Form) Draw(screen tcell.Screen) {
// Focus is called by the application when the primitive receives focus.
func (f *Form) Focus(delegate func(p Primitive)) {
if len(f.items)+len(f.buttons) == 0 {
f.Box.Focus(delegate)
return
}
f.hasFocus = false
// Hand on the focus to one of our child elements.
if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
f.focusedElement = 0
@ -702,22 +703,41 @@ func (f *Form) Focus(delegate func(p Primitive)) {
}
}
// Set the handler for all items and buttons.
// Track whether a form item has focus.
var itemFocused bool
f.hasFocus = false
// Set the handler and focus for all items and buttons.
for index, button := range f.buttons {
button.SetExitFunc(handler)
if f.focusedElement == index+len(f.items) {
if button.IsDisabled() {
f.focusedElement++
if f.focusedElement >= len(f.items)+len(f.buttons) {
f.focusedElement = 0
}
continue
}
itemFocused = true
func(b *Button) { // Wrapping might not be necessary anymore in future Go versions.
defer delegate(b)
}(button)
}
}
for index, item := range f.items {
item.SetFinishedFunc(handler)
if f.focusedElement == index {
itemFocused = true
func(i FormItem) { // Wrapping might not be necessary anymore in future Go versions.
defer delegate(i)
}(item)
}
}
for index, button := range f.buttons {
button.SetExitFunc(handler)
if f.focusedElement == index+len(f.items) {
func(b *Button) { // Wrapping might not be necessary anymore in future Go versions.
defer delegate(b)
}(button)
}
// If no item was focused, focus the form itself.
if !itemFocused {
f.Box.Focus(delegate)
}
}

@ -251,6 +251,11 @@ func (i *Image) GetFieldHeight() int {
return i.height
}
// SetDisabled sets whether or not the item is disabled / read-only.
func (i *Image) SetDisabled(disabled bool) FormItem {
return i // Images are always read-only.
}
// SetFormAttributes sets attributes shared by all form items.
func (i *Image) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
i.labelWidth = labelWidth

@ -49,6 +49,9 @@ const (
type InputField struct {
*Box
// Whether or not this input field is disabled/read-only.
disabled bool
// The text that was entered.
text string
@ -277,6 +280,15 @@ func (i *InputField) GetFieldHeight() int {
return 1
}
// SetDisabled sets whether or not the item is disabled / read-only.
func (i *InputField) SetDisabled(disabled bool) FormItem {
i.disabled = disabled
if i.finished != nil {
i.finished(-1)
}
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 {
@ -403,6 +415,18 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return i
}
// Focus is called when this primitive receives focus.
func (i *InputField) Focus(delegate func(p Primitive)) {
// If we're part of a form and this item is disabled, there's nothing the
// user can do here so we're finished.
if i.finished != nil && i.disabled {
i.finished(-1)
return
}
i.Box.Focus(delegate)
}
// Blur is called when this primitive loses focus.
func (i *InputField) Blur() {
i.Box.Blur()
@ -450,6 +474,9 @@ func (i *InputField) Draw(screen tcell.Screen) {
if rightLimit-x < fieldWidth {
fieldWidth = rightLimit - x
}
if i.disabled {
inputStyle = inputStyle.Background(i.backgroundColor)
}
if inputBg != tcell.ColorDefault {
for index := 0; index < fieldWidth; index++ {
screen.SetContent(x+index, y, ' ', nil, inputStyle)
@ -552,6 +579,10 @@ func (i *InputField) Draw(screen tcell.Screen) {
// InputHandler returns the handler for this primitive.
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if i.disabled {
return
}
// Trigger changed events.
currentText := i.text
defer func() {
@ -733,6 +764,10 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
// MouseHandler returns the mouse handler for this primitive.
func (i *InputField) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return i.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if i.disabled {
return false, nil
}
currentText := i.GetText()
defer func() {
if i.GetText() != currentText {

@ -192,6 +192,9 @@ type textAreaUndoItem struct {
type TextArea struct {
*Box
// Whether or not this text area is disabled/read-only.
disabled bool
// The size of the text area. If set to 0, the text area will use the entire
// available space.
width, height int
@ -749,6 +752,15 @@ func (t *TextArea) GetFieldHeight() int {
return t.height
}
// SetDisabled sets whether or not the item is disabled / read-only.
func (t *TextArea) SetDisabled(disabled bool) FormItem {
t.disabled = disabled
if t.finished != nil {
t.finished(-1)
}
return t
}
// SetMaxLength sets the maximum number of bytes allowed in the text area. A
// value of 0 means there is no limit. If the text area currently contains more
// bytes than this, it may violate this constraint.
@ -848,6 +860,18 @@ func (t *TextArea) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return t
}
// Focus is called when this primitive receives focus.
func (t *TextArea) Focus(delegate func(p Primitive)) {
// If we're part of a form and this item is disabled, there's nothing the
// user can do here so we're finished.
if t.finished != nil && t.disabled {
t.finished(-1)
return
}
t.Box.Focus(delegate)
}
// SetFormAttributes sets attributes shared by all form items.
func (t *TextArea) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
t.labelWidth = labelWidth
@ -1066,7 +1090,10 @@ func (t *TextArea) Draw(screen tcell.Screen) {
// Draw the input element if necessary.
_, bg, _ := t.textStyle.Decompose()
if bg != t.GetBackgroundColor() {
if t.disabled {
bg = t.backgroundColor
}
if bg != t.backgroundColor {
for row := 0; row < height; row++ {
for column := 0; column < width; column++ {
screen.SetContent(x+column, y+row, ' ', nil, t.textStyle)
@ -1144,6 +1171,9 @@ func (t *TextArea) Draw(screen tcell.Screen) {
fromRow > line ||
fromRow == line && fromColumn > posX {
style = t.textStyle
if t.disabled {
style = style.Background(t.backgroundColor)
}
}
// Draw character.
@ -1810,6 +1840,10 @@ func (t *TextArea) getSelectedText() string {
// InputHandler returns the handler for this primitive.
func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if t.disabled {
return
}
// All actions except a few specific ones are "other" actions.
newLastAction := taActionOther
defer func() {
@ -2197,6 +2231,10 @@ func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
// MouseHandler returns the mouse handler for this primitive.
func (t *TextArea) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if t.disabled {
return false, nil
}
x, y := event.Position()
rectX, rectY, _, _ := t.GetInnerRect()
if !t.InRect(x, y) {

@ -309,6 +309,11 @@ func (t *TextView) GetFieldHeight() int {
return t.height
}
// SetDisabled sets whether or not the item is disabled / read-only.
func (t *TextView) SetDisabled(disabled bool) FormItem {
return t // Text views are always read-only.
}
// SetScrollable sets the flag that decides whether or not the text view is
// scrollable. If true, text is kept in a buffer and can be navigated. If false,
// the last line will always be visible.
@ -760,7 +765,6 @@ func (t *TextView) Focus(delegate func(p Primitive)) {
// Implemented here with locking because this is used by layout primitives.
t.Lock()
defer t.Unlock()
t.Box.Focus(delegate)
// But if we're part of a form and not scrollable, there's nothing the user
// can do here so we're finished.
@ -768,6 +772,8 @@ func (t *TextView) Focus(delegate func(p Primitive)) {
t.finished(-1)
return
}
t.Box.Focus(delegate)
}
// HasFocus returns whether or not this primitive has focus.
@ -1148,7 +1154,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Draw the text element if necessary.
_, bg, _ := t.textStyle.Decompose()
if bg != t.GetBackgroundColor() {
if bg != t.backgroundColor {
for row := 0; row < height; row++ {
for column := 0; column < width; column++ {
screen.SetContent(x+column, y+row, ' ', nil, t.textStyle)

Loading…
Cancel
Save