Introduced horizontal item alignment in forms. Resolves #33

This commit is contained in:
Oliver 2018-01-16 20:45:54 +01:00
parent c105638ec3
commit aa25839cfa
4 changed files with 126 additions and 19 deletions

View File

@ -96,6 +96,11 @@ func (c *Checkbox) SetFormAttributes(label string, labelColor, bgColor, fieldTex
return c
}
// GetFieldLength returns this primitive's field length.
func (c *Checkbox) GetFieldLength() int {
return 1
}
// 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.

View File

@ -138,6 +138,21 @@ func (d *DropDown) SetFieldLength(length int) *DropDown {
return d
}
// GetFieldLength returns this primitive's field length.
func (d *DropDown) GetFieldLength() int {
if d.fieldLength > 0 {
return d.fieldLength
}
fieldLength := 0
for _, option := range d.options {
length := runewidth.StringWidth(option.Text)
if length > fieldLength {
fieldLength = length
}
}
return fieldLength
}
// 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 {

120
form.go
View File

@ -7,6 +7,11 @@ import (
runewidth "github.com/mattn/go-runewidth"
)
// DefaultFormFieldLength is the default field length of form elements whose
// field length is flexible (0). This is used in the Form class for horizontal
// layouts.
var DefaultFormFieldLength = 10
// FormItem is the interface all form items must implement to be able to be
// included in a form.
type FormItem interface {
@ -18,6 +23,11 @@ type FormItem interface {
// SetFormAttributes sets a number of item attributes at once.
SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
// GetFieldLength returns the length of the form item's field (the area which
// is manipulated by the user). A value of 0 indicates the the field length
// is flexible and may use as much space as required.
GetFieldLength() int
// SetEnteredFunc sets the handler function for when the user finished
// entering data into the item. The handler may receive events for the
// Enter key (we're done), the Escape key (cancel input), the Tab key (move to
@ -37,6 +47,10 @@ type Form struct {
// The buttons of the form.
buttons []*Button
// If set to true, instead of position items and buttons from top to bottom,
// they are positioned from left to right.
horizontal bool
// The alignment of the buttons.
buttonsAlign int
@ -85,12 +99,23 @@ func NewForm() *Form {
return f
}
// SetItemPadding sets the number of empty rows between form items.
// SetItemPadding sets the number of empty rows between form items for vertical
// layouts and the number of empty cells between form items for horizontal
// layouts.
func (f *Form) SetItemPadding(padding int) *Form {
f.itemPadding = padding
return f
}
// SetHorizontal sets the direction the form elements are laid out. If set to
// true, instead of positioning them from top to bottom (the default), they are
// positioned from left to right, moving into the next row if there is not
// enough space.
func (f *Form) SetHorizontal(horizontal bool) *Form {
f.horizontal = horizontal
return f
}
// SetLabelColor sets the color of the labels.
func (f *Form) SetLabelColor(color tcell.Color) *Form {
f.labelColor = color
@ -110,7 +135,7 @@ func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
}
// SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft
// (the default), AlignCenter, and AlignRight.
// (the default), AlignCenter, and AlignRight. This is only
func (f *Form) SetButtonsAlign(align int) *Form {
f.buttonsAlign = align
return f
@ -201,6 +226,14 @@ func (f *Form) Clear(includeButtons bool) *Form {
return f
}
// AddFormItem adds a new item to the form. This can be used to add your own
// objects to the form. Note, however, that the Form class will override some
// of its attributes to make it work in the form context.
func (f *Form) AddFormItem(item FormItem) *Form {
f.items = append(f.items, item)
return f
}
// GetFormItem returns the form element at the given position, starting with
// index 0. Elements are referenced in the order they were added. Buttons are
// not included.
@ -223,6 +256,7 @@ func (f *Form) Draw(screen tcell.Screen) {
x, y, width, height := f.GetInnerRect()
bottomLimit := y + height
rightLimit := x + width
startX := x
// Find the longest label.
var labelLength int
@ -237,23 +271,60 @@ func (f *Form) Draw(screen tcell.Screen) {
// Set up and draw the input fields.
for _, item := range f.items {
// Stop if there is no more space.
if y >= bottomLimit {
return // Stop here.
return
}
// Calculate the space needed.
label := strings.TrimSpace(item.GetLabel())
labelWidth := runewidth.StringWidth(label)
var itemWidth int
if f.horizontal {
fieldLength := item.GetFieldLength()
if fieldLength == 0 {
fieldLength = DefaultFormFieldLength
}
label += " "
labelWidth++
itemWidth = labelWidth + fieldLength
} else {
// We want all fields to align vertically.
label += strings.Repeat(" ", labelLength-labelWidth)
itemWidth = width
}
// Advance to next line if there is no space.
if f.horizontal && x+labelWidth+1 >= rightLimit {
x = startX
y += 2
}
// Adjust the item's attributes.
if x+itemWidth >= rightLimit {
itemWidth = rightLimit - x
}
item.SetFormAttributes(
label+strings.Repeat(" ", labelLength-runewidth.StringWidth(label)),
label,
f.labelColor,
f.backgroundColor,
f.fieldTextColor,
f.fieldBackgroundColor,
).SetRect(x, y, width, 1)
).SetRect(x, y, itemWidth, 1)
// Draw items with focus last (in case of overlaps).
if item.GetFocusable().HasFocus() {
defer item.Draw(screen)
} else {
item.Draw(screen)
}
y += 1 + f.itemPadding
// Advance to next item.
if f.horizontal {
x += itemWidth + f.itemPadding
} else {
y += 1 + f.itemPadding
}
}
// How wide are the buttons?
@ -262,32 +333,43 @@ func (f *Form) Draw(screen tcell.Screen) {
for index, button := range f.buttons {
width := runewidth.StringWidth(button.GetLabel()) + 4
buttonWidths[index] = width
buttonsWidth += width + 2
buttonsWidth += width + 1
}
buttonsWidth -= 2
buttonsWidth--
// Where do we place them?
if x+buttonsWidth < rightLimit {
if !f.horizontal && x+buttonsWidth < rightLimit {
if f.buttonsAlign == AlignRight {
x = rightLimit - buttonsWidth
} else if f.buttonsAlign == AlignCenter {
x = (x + rightLimit - buttonsWidth) / 2
}
// In vertical layouts, buttons always appear after an empty line.
if f.itemPadding == 0 {
y++
}
}
// Draw them.
if f.itemPadding == 0 {
y++
}
if y >= bottomLimit {
return // Stop here.
}
for index, button := range f.buttons {
space := rightLimit - x
if space < 1 {
break // No space for this button anymore.
if y >= bottomLimit {
return // Stop here.
}
space := rightLimit - x
buttonWidth := buttonWidths[index]
if f.horizontal {
if space < buttonWidth-4 {
x = startX
y += 2
space = width
}
} else {
if space < 1 {
break // No space for this button anymore.
}
}
if buttonWidth > space {
buttonWidth = space
}
@ -298,7 +380,7 @@ func (f *Form) Draw(screen tcell.Screen) {
SetRect(x, y, buttonWidth, 1)
button.Draw(screen)
x += buttonWidth + 2
x += buttonWidth + 1
}
}

View File

@ -125,6 +125,11 @@ func (i *InputField) SetFieldLength(length int) *InputField {
return i
}
// GetFieldLength returns this primitive's field length.
func (i *InputField) GetFieldLength() int {
return i.fieldLength
}
// SetMaskCharacter sets a character that masks user input on a screen. A value
// of 0 disables masking.
func (i *InputField) SetMaskCharacter(mask rune) *InputField {