package cview
import (
"reflect"
"sync"
"github.com/gdamore/tcell/v2"
)
var DefaultFormFieldWidth = 10
type FormItemAttributes struct {
LabelWidth int
BackgroundColor tcell .Color
LabelColor tcell .Color
LabelColorFocused tcell .Color
FieldBackgroundColor tcell .Color
FieldBackgroundColorFocused tcell .Color
FieldTextColor tcell .Color
FieldTextColorFocused tcell .Color
FinishedFunc func (key tcell .Key )
}
type FormItem interface {
Primitive
GetLabel () string
SetLabelWidth (int )
SetLabelColor (tcell .Color )
SetLabelColorFocused (tcell .Color )
GetFieldWidth () int
GetFieldHeight () int
SetFieldTextColor (tcell .Color )
SetFieldTextColorFocused (tcell .Color )
SetFieldBackgroundColor (tcell .Color )
SetFieldBackgroundColorFocused (tcell .Color )
SetBackgroundColor (tcell .Color )
SetFinishedFunc (func (key tcell .Key ))
}
type Form struct {
*Box
items []FormItem
buttons []*Button
horizontal bool
buttonsAlign int
itemPadding int
focusedElement int
wrapAround bool
labelColor tcell .Color
labelColorFocused tcell .Color
fieldBackgroundColor tcell .Color
fieldBackgroundColorFocused tcell .Color
fieldTextColor tcell .Color
fieldTextColorFocused tcell .Color
buttonBackgroundColor tcell .Color
buttonBackgroundColorFocused tcell .Color
buttonTextColor tcell .Color
buttonTextColorFocused tcell .Color
cancel func ()
sync .RWMutex
}
func NewForm () *Form {
box := NewBox ()
box .SetPadding (1 , 1 , 1 , 1 )
f := &Form {
Box : box ,
itemPadding : 1 ,
labelColor : Styles .SecondaryTextColor ,
fieldBackgroundColor : Styles .MoreContrastBackgroundColor ,
fieldBackgroundColorFocused : Styles .ContrastBackgroundColor ,
fieldTextColor : Styles .PrimaryTextColor ,
fieldTextColorFocused : Styles .PrimaryTextColor ,
buttonBackgroundColor : Styles .MoreContrastBackgroundColor ,
buttonBackgroundColorFocused : Styles .ContrastBackgroundColor ,
buttonTextColor : Styles .PrimaryTextColor ,
buttonTextColorFocused : Styles .PrimaryTextColor ,
labelColorFocused : ColorUnset ,
}
f .focus = f
return f
}
func (f *Form ) SetItemPadding (padding int ) {
f .Lock ()
defer f .Unlock ()
f .itemPadding = padding
}
func (f *Form ) SetHorizontal (horizontal bool ) {
f .Lock ()
defer f .Unlock ()
f .horizontal = horizontal
}
func (f *Form ) SetLabelColor (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .labelColor = color
}
func (f *Form ) SetLabelColorFocused (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .labelColorFocused = color
}
func (f *Form ) SetFieldBackgroundColor (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .fieldBackgroundColor = color
}
func (f *Form ) SetFieldBackgroundColorFocused (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .fieldBackgroundColorFocused = color
}
func (f *Form ) SetFieldTextColor (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .fieldTextColor = color
}
func (f *Form ) SetFieldTextColorFocused (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .fieldTextColorFocused = color
}
func (f *Form ) SetButtonsAlign (align int ) {
f .Lock ()
defer f .Unlock ()
f .buttonsAlign = align
}
func (f *Form ) SetButtonBackgroundColor (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .buttonBackgroundColor = color
}
func (f *Form ) SetButtonBackgroundColorFocused (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .buttonBackgroundColorFocused = color
}
func (f *Form ) SetButtonTextColor (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .buttonTextColor = color
}
func (f *Form ) SetButtonTextColorFocused (color tcell .Color ) {
f .Lock ()
defer f .Unlock ()
f .buttonTextColorFocused = color
}
func (f *Form ) SetFocus (index int ) {
f .Lock ()
defer f .Unlock ()
if index < 0 {
f .focusedElement = 0
} else if index >= len (f .items )+len (f .buttons ) {
f .focusedElement = len (f .items ) + len (f .buttons )
} else {
f .focusedElement = index
}
}
func (f *Form ) AddInputField (label , value string , fieldWidth int , accept func (textToCheck string , lastChar rune ) bool , changed func (text string )) {
f .Lock ()
defer f .Unlock ()
inputField := NewInputField ()
inputField .SetLabel (label )
inputField .SetText (value )
inputField .SetFieldWidth (fieldWidth )
inputField .SetAcceptanceFunc (accept )
inputField .SetChangedFunc (changed )
f .items = append (f .items , inputField )
}
func (f *Form ) AddPasswordField (label , value string , fieldWidth int , mask rune , changed func (text string )) {
f .Lock ()
defer f .Unlock ()
if mask == 0 {
mask = '*'
}
passwordField := NewInputField ()
passwordField .SetLabel (label )
passwordField .SetText (value )
passwordField .SetFieldWidth (fieldWidth )
passwordField .SetMaskCharacter (mask )
passwordField .SetChangedFunc (changed )
f .items = append (f .items , passwordField )
}
func (f *Form ) AddDropDownSimple (label string , initialOption int , selected func (index int , option *DropDownOption ), options ...string ) {
f .Lock ()
defer f .Unlock ()
dd := NewDropDown ()
dd .SetLabel (label )
dd .SetOptionsSimple (selected , options ...)
dd .SetCurrentOption (initialOption )
f .items = append (f .items , dd )
}
func (f *Form ) AddDropDown (label string , initialOption int , selected func (index int , option *DropDownOption ), options []*DropDownOption ) {
f .Lock ()
defer f .Unlock ()
dd := NewDropDown ()
dd .SetLabel (label )
dd .SetOptions (selected , options ...)
dd .SetCurrentOption (initialOption )
f .items = append (f .items , dd )
}
func (f *Form ) AddCheckBox (label string , message string , checked bool , changed func (checked bool )) {
f .Lock ()
defer f .Unlock ()
c := NewCheckBox ()
c .SetLabel (label )
c .SetMessage (message )
c .SetChecked (checked )
c .SetChangedFunc (changed )
f .items = append (f .items , c )
}
func (f *Form ) AddSlider (label string , current , max , increment int , changed func (value int )) {
f .Lock ()
defer f .Unlock ()
s := NewSlider ()
s .SetLabel (label )
s .SetMax (max )
s .SetProgress (current )
s .SetIncrement (increment )
s .SetChangedFunc (changed )
f .items = append (f .items , s )
}
func (f *Form ) AddButton (label string , selected func ()) {
f .Lock ()
defer f .Unlock ()
button := NewButton (label )
button .SetSelectedFunc (selected )
f .buttons = append (f .buttons , button )
}
func (f *Form ) GetButton (index int ) *Button {
f .RLock ()
defer f .RUnlock ()
return f .buttons [index ]
}
func (f *Form ) RemoveButton (index int ) {
f .Lock ()
defer f .Unlock ()
f .buttons = append (f .buttons [:index ], f .buttons [index +1 :]...)
}
func (f *Form ) GetButtonCount () int {
f .RLock ()
defer f .RUnlock ()
return len (f .buttons )
}
func (f *Form ) GetButtonIndex (label string ) int {
f .RLock ()
defer f .RUnlock ()
for index , button := range f .buttons {
if button .GetLabel () == label {
return index
}
}
return -1
}
func (f *Form ) Clear (includeButtons bool ) {
f .Lock ()
defer f .Unlock ()
f .items = nil
if includeButtons {
f .buttons = nil
}
f .focusedElement = 0
}
func (f *Form ) ClearButtons () {
f .Lock ()
defer f .Unlock ()
f .buttons = nil
}
func (f *Form ) AddFormItem (item FormItem ) {
f .Lock ()
defer f .Unlock ()
if reflect .ValueOf (item ).IsNil () {
panic ("Invalid FormItem" )
}
f .items = append (f .items , item )
}
func (f *Form ) GetFormItemCount () int {
f .RLock ()
defer f .RUnlock ()
return len (f .items )
}
func (f *Form ) IndexOfFormItem (item FormItem ) int {
f .l .RLock ()
defer f .l .RUnlock ()
for index , formItem := range f .items {
if item == formItem {
return index
}
}
return -1
}
func (f *Form ) GetFormItem (index int ) FormItem {
f .RLock ()
defer f .RUnlock ()
if index > len (f .items )-1 || index < 0 {
return nil
}
return f .items [index ]
}
func (f *Form ) RemoveFormItem (index int ) {
f .Lock ()
defer f .Unlock ()
f .items = append (f .items [:index ], f .items [index +1 :]...)
}
func (f *Form ) GetFormItemByLabel (label string ) FormItem {
f .RLock ()
defer f .RUnlock ()
for _ , item := range f .items {
if item .GetLabel () == label {
return item
}
}
return nil
}
func (f *Form ) GetFormItemIndex (label string ) int {
f .RLock ()
defer f .RUnlock ()
for index , item := range f .items {
if item .GetLabel () == label {
return index
}
}
return -1
}
func (f *Form ) GetFocusedItemIndex () (formItem , button int ) {
f .RLock ()
defer f .RUnlock ()
index := f .focusIndex ()
if index < 0 {
return -1 , -1
}
if index < len (f .items ) {
return index , -1
}
return -1 , index - len (f .items )
}
func (f *Form ) SetWrapAround (wrapAround bool ) {
f .Lock ()
defer f .Unlock ()
f .wrapAround = wrapAround
}
func (f *Form ) SetCancelFunc (callback func ()) {
f .Lock ()
defer f .Unlock ()
f .cancel = callback
}
func (f *Form ) GetAttributes () *FormItemAttributes {
f .Lock ()
defer f .Unlock ()
return f .getAttributes ()
}
func (f *Form ) getAttributes () *FormItemAttributes {
attrs := &FormItemAttributes {
BackgroundColor : f .backgroundColor ,
LabelColor : f .labelColor ,
FieldBackgroundColor : f .fieldBackgroundColor ,
FieldTextColor : f .fieldTextColor ,
}
if f .labelColorFocused == ColorUnset {
attrs .LabelColorFocused = f .labelColor
} else {
attrs .LabelColorFocused = f .labelColorFocused
}
if f .fieldBackgroundColorFocused == ColorUnset {
attrs .FieldBackgroundColorFocused = f .fieldTextColor
} else {
attrs .FieldBackgroundColorFocused = f .fieldBackgroundColorFocused
}
if f .fieldTextColorFocused == ColorUnset {
attrs .FieldTextColorFocused = f .fieldBackgroundColor
} else {
attrs .FieldTextColorFocused = f .fieldTextColorFocused
}
return attrs
}
func (f *Form ) Draw (screen tcell .Screen ) {
if !f .GetVisible () {
return
}
f .Box .Draw (screen )
f .Lock ()
defer f .Unlock ()
if index := f .focusIndex (); index >= 0 {
f .focusedElement = index
}
x , y , width , height := f .GetInnerRect ()
topLimit := y
bottomLimit := y + height
rightLimit := x + width
startX := x
var maxLabelWidth int
for _ , item := range f .items {
labelWidth := TaggedStringWidth (item .GetLabel ())
if labelWidth > maxLabelWidth {
maxLabelWidth = labelWidth
}
}
maxLabelWidth ++
positions := make ([]struct { x , y , width , height int }, len (f .items )+len (f .buttons ))
var focusedPosition struct { x , y , width , height int }
for index , item := range f .items {
if !item .GetVisible () {
continue
}
labelWidth := TaggedStringWidth (item .GetLabel ())
var itemWidth int
if f .horizontal {
fieldWidth := item .GetFieldWidth ()
if fieldWidth == 0 {
fieldWidth = DefaultFormFieldWidth
}
labelWidth ++
itemWidth = labelWidth + fieldWidth
} else {
labelWidth = maxLabelWidth
itemWidth = width
}
if f .horizontal && x +labelWidth +1 >= rightLimit {
x = startX
y += 2
}
if x +itemWidth >= rightLimit {
itemWidth = rightLimit - x
}
attributes := f .getAttributes ()
attributes .LabelWidth = labelWidth
setFormItemAttributes (item , attributes )
positions [index ].x = x
positions [index ].y = y
positions [index ].width = itemWidth
positions [index ].height = 1
if item .GetFocusable ().HasFocus () {
focusedPosition = positions [index ]
}
if f .horizontal {
x += itemWidth + f .itemPadding
} else {
y += item .GetFieldHeight () + f .itemPadding
}
}
buttonWidths := make ([]int , len (f .buttons ))
buttonsWidth := 0
for index , button := range f .buttons {
w := TaggedStringWidth (button .GetLabel ()) + 4
buttonWidths [index ] = w
buttonsWidth += w + 1
}
buttonsWidth --
if !f .horizontal && x +buttonsWidth < rightLimit {
if f .buttonsAlign == AlignRight {
x = rightLimit - buttonsWidth
} else if f .buttonsAlign == AlignCenter {
x = (x + rightLimit - buttonsWidth ) / 2
}
if f .itemPadding == 0 {
y ++
}
}
for index , button := range f .buttons {
if !button .GetVisible () {
continue
}
space := rightLimit - x
buttonWidth := buttonWidths [index ]
if f .horizontal {
if space < buttonWidth -4 {
x = startX
y += 2
space = width
}
} else {
if space < 1 {
break
}
}
if buttonWidth > space {
buttonWidth = space
}
button .SetLabelColor (f .buttonTextColor )
button .SetLabelColorFocused (f .buttonTextColorFocused )
button .SetBackgroundColorFocused (f .buttonBackgroundColorFocused )
button .SetBackgroundColor (f .buttonBackgroundColor )
buttonIndex := index + len (f .items )
positions [buttonIndex ].x = x
positions [buttonIndex ].y = y
positions [buttonIndex ].width = buttonWidth
positions [buttonIndex ].height = 1
if button .HasFocus () {
focusedPosition = positions [buttonIndex ]
}
x += buttonWidth + 1
}
var offset int
if focusedPosition .y +focusedPosition .height > bottomLimit {
offset = focusedPosition .y + focusedPosition .height - bottomLimit
if focusedPosition .y -offset < topLimit {
offset = focusedPosition .y - topLimit
}
}
for index , item := range f .items {
if !item .GetVisible () {
continue
}
y := positions [index ].y - offset
height := positions [index ].height
item .SetRect (positions [index ].x , y , positions [index ].width , height )
if y +height <= topLimit || y >= bottomLimit {
continue
}
if item .GetFocusable ().HasFocus () {
defer item .Draw (screen )
} else {
item .Draw (screen )
}
}
for index , button := range f .buttons {
if !button .GetVisible () {
continue
}
buttonIndex := index + len (f .items )
y := positions [buttonIndex ].y - offset
height := positions [buttonIndex ].height
button .SetRect (positions [buttonIndex ].x , y , positions [buttonIndex ].width , height )
if y +height <= topLimit || y >= bottomLimit {
continue
}
button .Draw (screen )
}
}
func (f *Form ) updateFocusedElement (decreasing bool ) {
li := len (f .items )
l := len (f .items ) + len (f .buttons )
for i := 0 ; i < l ; i ++ {
if f .focusedElement < 0 {
if f .wrapAround {
f .focusedElement = l - 1
} else {
f .focusedElement = 0
}
} else if f .focusedElement >= l {
if f .wrapAround {
f .focusedElement = 0
} else {
f .focusedElement = l - 1
}
}
if f .focusedElement < li {
item := f .items [f .focusedElement ]
if item .GetVisible () {
break
}
} else {
button := f .buttons [f .focusedElement -li ]
if button .GetVisible () {
break
}
}
if decreasing {
f .focusedElement --
} else {
f .focusedElement ++
}
}
}
func (f *Form ) formItemInputHandler (delegate func (p Primitive )) func (key tcell .Key ) {
return func (key tcell .Key ) {
f .Lock ()
switch key {
case tcell .KeyTab , tcell .KeyEnter :
f .focusedElement ++
f .updateFocusedElement (false )
f .Unlock ()
f .Focus (delegate )
f .Lock ()
case tcell .KeyBacktab :
f .focusedElement --
f .updateFocusedElement (true )
f .Unlock ()
f .Focus (delegate )
f .Lock ()
case tcell .KeyEscape :
if f .cancel != nil {
f .Unlock ()
f .cancel ()
f .Lock ()
} else {
f .focusedElement = 0
f .updateFocusedElement (true )
f .Unlock ()
f .Focus (delegate )
f .Lock ()
}
}
f .Unlock ()
}
}
func (f *Form ) Focus (delegate func (p Primitive )) {
f .Lock ()
if len (f .items )+len (f .buttons ) == 0 {
f .hasFocus = true
f .Unlock ()
return
}
f .hasFocus = false
if f .focusedElement < 0 || f .focusedElement >= len (f .items )+len (f .buttons ) {
f .focusedElement = 0
}
if f .focusedElement < len (f .items ) {
item := f .items [f .focusedElement ]
attributes := f .getAttributes ()
attributes .FinishedFunc = f .formItemInputHandler (delegate )
f .Unlock ()
setFormItemAttributes (item , attributes )
delegate (item )
} else {
button := f .buttons [f .focusedElement -len (f .items )]
button .SetBlurFunc (f .formItemInputHandler (delegate ))
f .Unlock ()
delegate (button )
}
}
func (f *Form ) HasFocus () bool {
f .Lock ()
defer f .Unlock ()
if f .hasFocus {
return true
}
return f .focusIndex () >= 0
}
func (f *Form ) focusIndex () int {
for index , item := range f .items {
if item .GetVisible () && item .GetFocusable ().HasFocus () {
return index
}
}
for index , button := range f .buttons {
if button .GetVisible () && button .focus .HasFocus () {
return len (f .items ) + index
}
}
return -1
}
func (f *Form ) MouseHandler () func (action MouseAction , event *tcell .EventMouse , setFocus func (p Primitive )) (consumed bool , capture Primitive ) {
return f .WrapMouseHandler (func (action MouseAction , event *tcell .EventMouse , setFocus func (p Primitive )) (consumed bool , capture Primitive ) {
if !f .InRect (event .Position ()) {
return false , nil
}
for _ , item := range f .items {
consumed , capture = item .MouseHandler ()(action , event , setFocus )
if consumed {
return
}
}
for _ , button := range f .buttons {
consumed , capture = button .MouseHandler ()(action , event , setFocus )
if consumed {
return
}
}
if action == MouseLeftClick {
if f .focusedElement < len (f .items ) {
setFocus (f .items [f .focusedElement ])
} else if f .focusedElement < len (f .items )+len (f .buttons ) {
setFocus (f .buttons [f .focusedElement -len (f .items )])
}
consumed = true
}
return
})
}
func setFormItemAttributes(item FormItem , attrs *FormItemAttributes ) {
item .SetLabelWidth (attrs .LabelWidth )
item .SetBackgroundColor (attrs .BackgroundColor )
item .SetLabelColor (attrs .LabelColor )
item .SetLabelColorFocused (attrs .LabelColorFocused )
item .SetFieldTextColor (attrs .FieldTextColor )
item .SetFieldTextColorFocused (attrs .FieldTextColorFocused )
item .SetFieldBackgroundColor (attrs .FieldBackgroundColor )
item .SetFieldBackgroundColorFocused (attrs .FieldBackgroundColorFocused )
if attrs .FinishedFunc != nil {
item .SetFinishedFunc (attrs .FinishedFunc )
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .