[GH-ISSUE #413] SetLabelColor() does not work, SetLabel("[color]...") does #301

Closed
opened 2026-03-04 01:03:46 +03:00 by kerem · 7 comments
Owner

Originally created by @gwijnja on GitHub (Feb 26, 2020).
Original GitHub issue: https://github.com/rivo/tview/issues/413

What's wrong with the code below?

I am trying to set the label color using InputField.SetChangedFunc(), but the label color remains yellow. It works if I replace the SetLabelColor lines with i.e. SetLabel("[red]Name"). According to the tview wiki it is not necessary to call any Draw() function to redraw the label.

package main

import (
        "github.com/gdamore/tcell"
        "github.com/rivo/tview"
)

func main() {
        app := tview.NewApplication()

        nameField := tview.NewInputField().SetLabel("Name")
        nameField.SetChangedFunc(func(name string) {
                if len(name) == 0 {
                        nameField.SetLabelColor(tcell.ColorRed)
                } else {
                        nameField.SetLabelColor(tcell.ColorGreen)
                }
        })

        form := tview.NewForm().
                AddFormItem(nameField).
                AddButton("Ok", func() { app.Stop() })

        if err := app.SetRoot(form, true).Run(); err != nil {
                panic(err)
        }
}
Originally created by @gwijnja on GitHub (Feb 26, 2020). Original GitHub issue: https://github.com/rivo/tview/issues/413 What's wrong with the code below? I am trying to set the label color using `InputField.SetChangedFunc()`, but the label color remains yellow. It works if I replace the `SetLabelColor` lines with i.e. `SetLabel("[red]Name")`. According to [the tview wiki](https://github.com/rivo/tview/wiki/Concurrency#event-handlers) it is not necessary to call any Draw() function to redraw the label. ```golang package main import ( "github.com/gdamore/tcell" "github.com/rivo/tview" ) func main() { app := tview.NewApplication() nameField := tview.NewInputField().SetLabel("Name") nameField.SetChangedFunc(func(name string) { if len(name) == 0 { nameField.SetLabelColor(tcell.ColorRed) } else { nameField.SetLabelColor(tcell.ColorGreen) } }) form := tview.NewForm(). AddFormItem(nameField). AddButton("Ok", func() { app.Stop() }) if err := app.SetRoot(form, true).Run(); err != nil { panic(err) } } ```
kerem closed this issue 2026-03-04 01:03:46 +03:00
Author
Owner

@gnojus commented on GitHub (Feb 26, 2020):

Form overrides all of its child attributes. Therefore you need to use form.SetLabelColor(tcell.ColorRed)

<!-- gh-comment-id:591552688 --> @gnojus commented on GitHub (Feb 26, 2020): Form overrides all of its child attributes. Therefore you need to use `form.SetLabelColor(tcell.ColorRed)`
Author
Owner

@gwijnja commented on GitHub (Feb 26, 2020):

But I want the label color to indicate the validation result. If I have many fields, all fields would change color simultaneously...

<!-- gh-comment-id:591558761 --> @gwijnja commented on GitHub (Feb 26, 2020): But I want the label color to indicate the validation result. If I have many fields, all fields would change color simultaneously...
Author
Owner

@gwijnja commented on GitHub (Feb 26, 2020):

I found it. Form has property items []FormItem (including the InputFields). Whenever Form.Draw() is executed, all form items get their attributes overwritten with the Form's color properties.

in form.go:

for index, item := range f.items {
        // ...other code...
        item.SetFormAttributes(
                labelWidth,
                f.labelColor,
                f.backgroundColor,
                f.fieldTextColor,
                f.fieldBackgroundColor,
        )

So whatever is set with InputField.SetLabelColor() (and checkbox, and dropdown) is ignored and overwritten by the form.

And indeed, if I comment that part of the code and replace it with a type switch to only set the label width and not overwrite the colors, it works as expected:

switch v := item.(type) {
case *Checkbox:
        v.SetLabelWidth(labelWidth)
case *DropDown:
        v.SetLabelWidth(labelWidth)
case *InputField:
        v.SetLabelWidth(labelWidth)
}

But this probably breaks Form.SetLabelColor(). Maybe it's better to have a system where you can update all label colors through the Form, but override it from FormItems. So an update to a Form has priority over the defaults, but an update to a FormItem has priority over the Form.

<!-- gh-comment-id:591580667 --> @gwijnja commented on GitHub (Feb 26, 2020): I found it. `Form` has property `items []FormItem` (including the InputFields). Whenever `Form.Draw()` is executed, all form items get their attributes overwritten with the Form's color properties. in form.go: ```golang for index, item := range f.items { // ...other code... item.SetFormAttributes( labelWidth, f.labelColor, f.backgroundColor, f.fieldTextColor, f.fieldBackgroundColor, ) ``` So whatever is set with `InputField.SetLabelColor()` (and checkbox, and dropdown) is ignored and overwritten by the form. And indeed, if I comment that part of the code and replace it with a type switch to only set the label width and not overwrite the colors, it works as expected: ```golang switch v := item.(type) { case *Checkbox: v.SetLabelWidth(labelWidth) case *DropDown: v.SetLabelWidth(labelWidth) case *InputField: v.SetLabelWidth(labelWidth) } ``` But this probably breaks `Form.SetLabelColor()`. Maybe it's better to have a system where you can update all label colors through the Form, but override it from FormItems. So an update to a Form has priority over the defaults, but an update to a FormItem has priority over the Form.
Author
Owner

@gwijnja commented on GitHub (Feb 26, 2020):

How about this solution instead: keep the Form.go code as it is and change the SetFormAttributes method in InputField.go/Checkbox.go/DropDown.go (and Button.go?) so that colors are only changed if they match the default colors:

// SetFormAttributes sets attributes shared by all form items.
func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
        i.labelWidth = labelWidth
        if i.labelColor == Styles.SecondaryTextColor {
                i.labelColor = labelColor
        }
        if i.backgroundColor == Styles.ContrastBackgroundColor {
                i.backgroundColor = bgColor
        }
        if i.fieldTextColor == Styles.PrimaryTextColor {
                i.fieldTextColor = fieldTextColor
        }
        if i.fieldBackgroundColor == Styles.ContrastSecondaryTextColor {
                i.fieldBackgroundColor = fieldBgColor
        }
        return i
}

The result is that Form.SetXxxColor applies to all form elements that still have their default colors, and individual form elements can be overriden with [FormItem].SetXxxColor etc.

Only if you update all colors through Form.SetLabelColor() and then try to override a single element's color to the default color, it would be overridden again. If we want to fix that as well, then we need a flag for each color to remember if it was changed.

<!-- gh-comment-id:591604469 --> @gwijnja commented on GitHub (Feb 26, 2020): How about this solution instead: keep the Form.go code as it is and change the `SetFormAttributes` method in InputField.go/Checkbox.go/DropDown.go (and Button.go?) so that colors are only changed if they match the default colors: ```golang // SetFormAttributes sets attributes shared by all form items. func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { i.labelWidth = labelWidth if i.labelColor == Styles.SecondaryTextColor { i.labelColor = labelColor } if i.backgroundColor == Styles.ContrastBackgroundColor { i.backgroundColor = bgColor } if i.fieldTextColor == Styles.PrimaryTextColor { i.fieldTextColor = fieldTextColor } if i.fieldBackgroundColor == Styles.ContrastSecondaryTextColor { i.fieldBackgroundColor = fieldBgColor } return i } ``` The result is that `Form.SetXxxColor` applies to all form elements that still have their default colors, and individual form elements can be overriden with `[FormItem].SetXxxColor` etc. Only if you update all colors through `Form.SetLabelColor()` and then try to override a single element's color to the default color, it would be overridden again. If we want to fix that as well, then we need a flag for each color to remember if it was changed.
Author
Owner

@gwijnja commented on GitHub (Feb 26, 2020):

And now with flags. This would be the whole change, for each FormItem. The backgroundColor does not need to be checked, because there is no InputField.SetBackgroundColor() method.

diff --git a/inputfield.go b/inputfield.go
index 3cd255e..e38f7b7 100644
--- a/inputfield.go
+++ b/inputfield.go
@@ -44,12 +44,15 @@ type InputField struct {

        // The label color.
        labelColor tcell.Color
+       labelColorChanged bool

        // The background color of the input area.
        fieldBackgroundColor tcell.Color
+       fieldBackgroundColorChanged bool

        // The text color of the input area.
        fieldTextColor tcell.Color
+       fieldTextColorChanged bool

        // The text color of the placeholder.
        placeholderTextColor tcell.Color
@@ -151,18 +154,21 @@ func (i *InputField) SetPlaceholder(text string) *InputField {
 // SetLabelColor sets the color of the label.
 func (i *InputField) SetLabelColor(color tcell.Color) *InputField {
        i.labelColor = color
+       i.labelColorChanged = true
        return i
 }

 // SetFieldBackgroundColor sets the background color of the input area.
 func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {
        i.fieldBackgroundColor = color
+       i.fieldBackgroundColorChanged = true
        return i
 }

 // SetFieldTextColor sets the text color of the input area.
 func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {
        i.fieldTextColor = color
+       i.fieldTextColorChanged = true
        return i
 }

@@ -175,10 +181,16 @@ func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
 // SetFormAttributes sets attributes shared by all form items.
 func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Col
        i.labelWidth = labelWidth
-       i.labelColor = labelColor
+       if !i.labelColorChanged {
+               i.labelColor = labelColor
+       }
        i.backgroundColor = bgColor
-       i.fieldTextColor = fieldTextColor
-       i.fieldBackgroundColor = fieldBgColor
+       if !i.fieldTextColorChanged {
+               i.fieldTextColor = fieldTextColor
+       }
+       if !i.fieldBackgroundColorChanged {
+               i.fieldBackgroundColor = fieldBgColor
+       }
        return i
 }
<!-- gh-comment-id:591615762 --> @gwijnja commented on GitHub (Feb 26, 2020): And now with flags. This would be the whole change, for each FormItem. The backgroundColor does not need to be checked, because there is no `InputField.SetBackgroundColor()` method. ```patch diff --git a/inputfield.go b/inputfield.go index 3cd255e..e38f7b7 100644 --- a/inputfield.go +++ b/inputfield.go @@ -44,12 +44,15 @@ type InputField struct { // The label color. labelColor tcell.Color + labelColorChanged bool // The background color of the input area. fieldBackgroundColor tcell.Color + fieldBackgroundColorChanged bool // The text color of the input area. fieldTextColor tcell.Color + fieldTextColorChanged bool // The text color of the placeholder. placeholderTextColor tcell.Color @@ -151,18 +154,21 @@ func (i *InputField) SetPlaceholder(text string) *InputField { // SetLabelColor sets the color of the label. func (i *InputField) SetLabelColor(color tcell.Color) *InputField { i.labelColor = color + i.labelColorChanged = true return i } // SetFieldBackgroundColor sets the background color of the input area. func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField { i.fieldBackgroundColor = color + i.fieldBackgroundColorChanged = true return i } // SetFieldTextColor sets the text color of the input area. func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField { i.fieldTextColor = color + i.fieldTextColorChanged = true return i } @@ -175,10 +181,16 @@ func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField { // SetFormAttributes sets attributes shared by all form items. func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Col i.labelWidth = labelWidth - i.labelColor = labelColor + if !i.labelColorChanged { + i.labelColor = labelColor + } i.backgroundColor = bgColor - i.fieldTextColor = fieldTextColor - i.fieldBackgroundColor = fieldBgColor + if !i.fieldTextColorChanged { + i.fieldTextColor = fieldTextColor + } + if !i.fieldBackgroundColorChanged { + i.fieldBackgroundColor = fieldBgColor + } return i } ```
Author
Owner

@rivo commented on GitHub (Aug 18, 2020):

I could introduce a way to "intercept" these changes to form elements. A bit similar to SetInputCapture() or SetMouseCapture(). So you can decide yourself which attribute the form can override and which one it can't.

How does that sound to you?

<!-- gh-comment-id:675399544 --> @rivo commented on GitHub (Aug 18, 2020): I could introduce a way to "intercept" these changes to form elements. A bit similar to [`SetInputCapture()`](https://pkg.go.dev/github.com/rivo/tview?tab=doc#Box.SetInputCapture) or [`SetMouseCapture()`](https://pkg.go.dev/github.com/rivo/tview?tab=doc#Box.SetMouseCapture). So you can decide yourself which attribute the form can override and which one it can't. How does that sound to you?
Author
Owner

@rivo commented on GitHub (Jan 11, 2021):

Please open a new issue and reference this one if this is still important to you.

<!-- gh-comment-id:757904615 --> @rivo commented on GitHub (Jan 11, 2021): Please open a new issue and reference this one if this is still important to you.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#301
No description provided.