mirror of
https://github.com/rivo/tview.git
synced 2025-04-24 13:48:56 +08:00
Text area can now be added to forms. See #594
This commit is contained in:
parent
0b2ae10823
commit
ed3ea789e9
4
box.go
4
box.go
@ -336,10 +336,8 @@ func (b *Box) DrawForSubclass(screen tcell.Screen, p Primitive) {
|
||||
return
|
||||
}
|
||||
|
||||
def := tcell.StyleDefault
|
||||
|
||||
// Fill background.
|
||||
background := def.Background(b.backgroundColor)
|
||||
background := tcell.StyleDefault.Background(b.backgroundColor)
|
||||
if !b.dontClear {
|
||||
for y := b.y; y < b.y+b.height; y++ {
|
||||
for x := b.x; x < b.x+b.width; x++ {
|
||||
|
11
checkbox.go
11
checkbox.go
@ -130,6 +130,11 @@ func (c *Checkbox) GetFieldWidth() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// GetFieldHeight returns this primitive's field height.
|
||||
func (c *Checkbox) GetFieldHeight() 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.
|
||||
@ -170,13 +175,13 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
|
||||
// Draw label.
|
||||
if c.labelWidth > 0 {
|
||||
labelWidth := c.labelWidth
|
||||
if labelWidth > rightLimit-x {
|
||||
labelWidth = rightLimit - x
|
||||
if labelWidth > width {
|
||||
labelWidth = width
|
||||
}
|
||||
Print(screen, c.label, x, y, labelWidth, AlignLeft, c.labelColor)
|
||||
x += labelWidth
|
||||
} else {
|
||||
_, drawnWidth := Print(screen, c.label, x, y, rightLimit-x, AlignLeft, c.labelColor)
|
||||
_, drawnWidth := Print(screen, c.label, x, y, width, AlignLeft, c.labelColor)
|
||||
x += drawnWidth
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ func main() {
|
||||
AddDropDown("Title", []string{"Mr.", "Ms.", "Mrs.", "Dr.", "Prof."}, 0, nil).
|
||||
AddInputField("First name", "", 20, nil, nil).
|
||||
AddInputField("Last name", "", 20, nil, nil).
|
||||
AddTextArea("Address", "", 40, 0, 0, nil).
|
||||
AddCheckbox("Age 18+", false, nil).
|
||||
AddPasswordField("Password", "", 10, '*', nil).
|
||||
AddButton("Save", nil).
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 52 KiB |
@ -77,6 +77,7 @@ Type to enter text.
|
||||
|
||||
Move while holding Shift or drag the mouse.
|
||||
Double-click to select a word.
|
||||
[yellow]Ctrl-L[white] to select entire text.
|
||||
|
||||
[green]Clipboard
|
||||
|
||||
|
@ -244,6 +244,11 @@ func (d *DropDown) GetFieldWidth() int {
|
||||
return fieldWidth
|
||||
}
|
||||
|
||||
// GetFieldHeight returns this primitive's field height.
|
||||
func (d *DropDown) GetFieldHeight() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
79
form.go
79
form.go
@ -4,10 +4,16 @@ import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
// DefaultFormFieldWidth is the default field screen width of form elements
|
||||
// whose field width is flexible (0). This is used in the Form class for
|
||||
// horizontal layouts.
|
||||
var DefaultFormFieldWidth = 10
|
||||
var (
|
||||
// DefaultFormFieldWidth is the default field screen width of form elements
|
||||
// whose field width is flexible (0). This is used in the Form class for
|
||||
// horizontal layouts.
|
||||
DefaultFormFieldWidth = 10
|
||||
|
||||
// DefaultFormFieldHeight is the default field height of multi-line form
|
||||
// elements whose field height is flexible (0).
|
||||
DefaultFormFieldHeight = 5
|
||||
)
|
||||
|
||||
// FormItem is the interface all form items must implement to be able to be
|
||||
// included in a form.
|
||||
@ -26,6 +32,10 @@ type FormItem interface {
|
||||
// required.
|
||||
GetFieldWidth() int
|
||||
|
||||
// GetFieldHeight returns the height of the form item's field (the area which
|
||||
// is manipulated by the user). This value must be greater than 0.
|
||||
GetFieldHeight() int
|
||||
|
||||
// SetFinishedFunc 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
|
||||
@ -55,7 +65,7 @@ type Form struct {
|
||||
// The alignment of the buttons.
|
||||
buttonsAlign int
|
||||
|
||||
// The number of empty rows between items.
|
||||
// The number of empty cells between items.
|
||||
itemPadding int
|
||||
|
||||
// The index of the item or button which has focus. (Items are counted first,
|
||||
@ -167,6 +177,36 @@ func (f *Form) SetFocus(index int) *Form {
|
||||
return f
|
||||
}
|
||||
|
||||
// AddTextArea adds a text area to the form. It has a label, an optional initial
|
||||
// text, a size (width and height) referring to the actual input area (a
|
||||
// fieldWidth of 0 extends it as far right as possible, a fieldHeight of 0 will
|
||||
// cause it to be [DefaultFormFieldHeight]), and a maximum number of bytes of
|
||||
// text allowed (0 means no limit).
|
||||
//
|
||||
// The optional callback function is invoked when the content of the text area
|
||||
// has changed. Note that especially for larger texts, this is an expensive
|
||||
// operation due to technical constraints of the [TextArea] primitive (every key
|
||||
// stroke leads to a new reallocation of the entire text).
|
||||
func (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLength int, changed func(text string)) *Form {
|
||||
if fieldHeight == 0 {
|
||||
fieldHeight = DefaultFormFieldHeight
|
||||
}
|
||||
textArea := NewTextArea().
|
||||
SetLabel(label).
|
||||
SetSize(fieldHeight, fieldWidth).
|
||||
SetMaxLength(maxLength)
|
||||
if text != "" {
|
||||
textArea.SetText(text, true)
|
||||
}
|
||||
if changed != nil {
|
||||
textArea.SetChangedFunc(func() {
|
||||
changed(textArea.GetText())
|
||||
})
|
||||
}
|
||||
f.items = append(f.items, textArea)
|
||||
return f
|
||||
}
|
||||
|
||||
// AddInputField adds an input field to the form. It has a label, an optional
|
||||
// initial value, a field width (a value of 0 extends it as far as possible),
|
||||
// an optional accept function to validate the item's value (set to nil to
|
||||
@ -386,15 +426,19 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
maxLabelWidth++ // Add one space.
|
||||
|
||||
// Calculate positions of form items.
|
||||
positions := make([]struct{ x, y, width, height int }, len(f.items)+len(f.buttons))
|
||||
var focusedPosition struct{ x, y, width, height int }
|
||||
type position struct{ x, y, width, height int }
|
||||
positions := make([]position, len(f.items)+len(f.buttons))
|
||||
var (
|
||||
focusedPosition position
|
||||
lineHeight = 1
|
||||
)
|
||||
for index, item := range f.items {
|
||||
// Calculate the space needed.
|
||||
labelWidth := TaggedStringWidth(item.GetLabel())
|
||||
var itemWidth int
|
||||
if f.horizontal {
|
||||
fieldWidth := item.GetFieldWidth()
|
||||
if fieldWidth == 0 {
|
||||
if fieldWidth <= 0 {
|
||||
fieldWidth = DefaultFormFieldWidth
|
||||
}
|
||||
labelWidth++
|
||||
@ -404,11 +448,21 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
labelWidth = maxLabelWidth
|
||||
itemWidth = width
|
||||
}
|
||||
itemHeight := item.GetFieldHeight()
|
||||
if itemHeight <= 0 {
|
||||
itemHeight = DefaultFormFieldHeight
|
||||
}
|
||||
|
||||
// Advance to next line if there is no space.
|
||||
if f.horizontal && x+labelWidth+1 >= rightLimit {
|
||||
x = startX
|
||||
y += 2
|
||||
y += lineHeight + 1
|
||||
lineHeight = itemHeight
|
||||
}
|
||||
|
||||
// Update line height.
|
||||
if itemHeight > lineHeight {
|
||||
lineHeight = itemHeight
|
||||
}
|
||||
|
||||
// Adjust the item's attributes.
|
||||
@ -427,7 +481,7 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
positions[index].x = x
|
||||
positions[index].y = y
|
||||
positions[index].width = itemWidth
|
||||
positions[index].height = 1
|
||||
positions[index].height = itemHeight
|
||||
if item.HasFocus() {
|
||||
focusedPosition = positions[index]
|
||||
}
|
||||
@ -436,7 +490,7 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
if f.horizontal {
|
||||
x += itemWidth + f.itemPadding
|
||||
} else {
|
||||
y += 1 + f.itemPadding
|
||||
y += itemHeight + f.itemPadding
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,8 +525,9 @@ func (f *Form) Draw(screen tcell.Screen) {
|
||||
if f.horizontal {
|
||||
if space < buttonWidth-4 {
|
||||
x = startX
|
||||
y += 2
|
||||
y += lineHeight + 1
|
||||
space = width
|
||||
lineHeight = 1
|
||||
}
|
||||
} else {
|
||||
if space < 1 {
|
||||
|
@ -249,6 +249,11 @@ func (i *InputField) GetFieldWidth() int {
|
||||
return i.fieldWidth
|
||||
}
|
||||
|
||||
// GetFieldHeight returns this primitive's field height.
|
||||
func (i *InputField) GetFieldHeight() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// SetMaskCharacter sets a character that masks user input on a screen. A value
|
||||
// of 0 disables masking.
|
||||
func (i *InputField) SetMaskCharacter(mask rune) *InputField {
|
||||
@ -372,13 +377,13 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
||||
_, labelBg, _ := i.labelStyle.Decompose()
|
||||
if i.labelWidth > 0 {
|
||||
labelWidth := i.labelWidth
|
||||
if labelWidth > rightLimit-x {
|
||||
labelWidth = rightLimit - x
|
||||
if labelWidth > width {
|
||||
labelWidth = width
|
||||
}
|
||||
printWithStyle(screen, i.label, x, y, 0, labelWidth, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
|
||||
x += labelWidth
|
||||
} else {
|
||||
_, drawnWidth, _, _ := printWithStyle(screen, i.label, x, y, 0, rightLimit-x, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
|
||||
_, drawnWidth, _, _ := printWithStyle(screen, i.label, x, y, 0, width, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
|
||||
x += drawnWidth
|
||||
}
|
||||
|
||||
|
138
textarea.go
138
textarea.go
@ -192,11 +192,24 @@ type textAreaUndoItem struct {
|
||||
type TextArea struct {
|
||||
*Box
|
||||
|
||||
// The size of the text area. If set to 0, the text area will use the entire
|
||||
// available space.
|
||||
width, height int
|
||||
|
||||
// The text to be shown in the text area when it is empty.
|
||||
placeholder string
|
||||
|
||||
// The label text shown, usually when part of a form.
|
||||
label string
|
||||
|
||||
// The width of the text area's label.
|
||||
labelWidth int
|
||||
|
||||
// Styles:
|
||||
|
||||
// The label style.
|
||||
labelStyle tcell.Style
|
||||
|
||||
// The style of the text. Background colors different from the Box's
|
||||
// background color may lead to unwanted artefacts.
|
||||
textStyle tcell.Style
|
||||
@ -314,6 +327,10 @@ type TextArea struct {
|
||||
// An optional function which is called when the position of the cursor or
|
||||
// the selection has changed.
|
||||
moved func()
|
||||
|
||||
// A callback function set by the Form class and called when the user leaves
|
||||
// this form item.
|
||||
finished func(tcell.Key)
|
||||
}
|
||||
|
||||
// NewTextArea returns a new text area. Use [TextArea.SetText] to set the
|
||||
@ -324,6 +341,7 @@ func NewTextArea() *TextArea {
|
||||
wrap: true,
|
||||
wordWrap: true,
|
||||
placeholderStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.TertiaryTextColor),
|
||||
labelStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),
|
||||
textStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),
|
||||
selectedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor),
|
||||
spans: make([]textAreaSpan, 2, pieceChainMinCap), // We reserve some space to avoid reallocations right when editing starts.
|
||||
@ -693,6 +711,44 @@ func (t *TextArea) SetPlaceholder(placeholder string) *TextArea {
|
||||
return t
|
||||
}
|
||||
|
||||
// SetLabel sets the text to be displayed before the text area.
|
||||
func (t *TextArea) SetLabel(label string) *TextArea {
|
||||
t.label = label
|
||||
return t
|
||||
}
|
||||
|
||||
// GetLabel returns the text to be displayed before the text area.
|
||||
func (t *TextArea) GetLabel() string {
|
||||
return t.label
|
||||
}
|
||||
|
||||
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
||||
// primitive to use the width of the label string.
|
||||
func (t *TextArea) SetLabelWidth(width int) *TextArea {
|
||||
t.labelWidth = width
|
||||
return t
|
||||
}
|
||||
|
||||
// SetSize sets the screen size of the input element of the text area. The input
|
||||
// element is always located next to the label which is always located in the
|
||||
// top left corner. If any of the values are 0 or larger than the available
|
||||
// space, the available space will be used.
|
||||
func (t *TextArea) SetSize(rows, columns int) *TextArea {
|
||||
t.width = columns
|
||||
t.height = rows
|
||||
return t
|
||||
}
|
||||
|
||||
// GetFieldWidth returns this primitive's field width.
|
||||
func (t *TextArea) GetFieldWidth() int {
|
||||
return t.width
|
||||
}
|
||||
|
||||
// GetFieldHeight returns this primitive's field height.
|
||||
func (t *TextArea) GetFieldHeight() int {
|
||||
return t.height
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -701,6 +757,17 @@ func (t *TextArea) SetMaxLength(maxLength int) *TextArea {
|
||||
return t
|
||||
}
|
||||
|
||||
// SetLabelStyle sets the style of the label.
|
||||
func (t *TextArea) SetLabelStyle(style tcell.Style) *TextArea {
|
||||
t.labelStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// GetLabelStyle returns the style of the label.
|
||||
func (t *TextArea) GetLabelStyle() tcell.Style {
|
||||
return t.labelStyle
|
||||
}
|
||||
|
||||
// SetTextStyle sets the style of the text. Background colors different from the
|
||||
// Box's background color may lead to unwanted artefacts.
|
||||
func (t *TextArea) SetTextStyle(style tcell.Style) *TextArea {
|
||||
@ -775,6 +842,21 @@ func (t *TextArea) SetMovedFunc(handler func()) *TextArea {
|
||||
return t
|
||||
}
|
||||
|
||||
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
|
||||
func (t *TextArea) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
||||
t.finished = handler
|
||||
return t
|
||||
}
|
||||
|
||||
// SetFormAttributes sets attributes shared by all form items.
|
||||
func (t *TextArea) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
|
||||
t.labelWidth = labelWidth
|
||||
t.backgroundColor = bgColor
|
||||
t.labelStyle = t.labelStyle.Foreground(labelColor)
|
||||
t.textStyle = tcell.StyleDefault.Foreground(fieldTextColor).Background(fieldBgColor)
|
||||
return t
|
||||
}
|
||||
|
||||
// replace deletes a range of text and inserts the given text at that position.
|
||||
// If the resulting text would exceed the maximum length, the function does not
|
||||
// do anything. The function returns the end position of the deleted/inserted
|
||||
@ -947,7 +1029,7 @@ func (t *TextArea) Draw(screen tcell.Screen) {
|
||||
|
||||
// Prepare
|
||||
x, y, width, height := t.GetInnerRect()
|
||||
if width == 0 || height == 0 {
|
||||
if width <= 0 || height <= 0 {
|
||||
return // We have no space for anything.
|
||||
}
|
||||
columnOffset := t.columnOffset
|
||||
@ -955,6 +1037,43 @@ func (t *TextArea) Draw(screen tcell.Screen) {
|
||||
columnOffset = 0
|
||||
}
|
||||
|
||||
// Draw label.
|
||||
_, labelBg, _ := t.labelStyle.Decompose()
|
||||
if t.labelWidth > 0 {
|
||||
labelWidth := t.labelWidth
|
||||
if labelWidth > width {
|
||||
labelWidth = width
|
||||
}
|
||||
printWithStyle(screen, t.label, x, y, 0, labelWidth, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)
|
||||
x += labelWidth
|
||||
width -= labelWidth
|
||||
} else {
|
||||
_, drawnWidth, _, _ := printWithStyle(screen, t.label, x, y, 0, width, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)
|
||||
x += drawnWidth
|
||||
width -= drawnWidth
|
||||
}
|
||||
|
||||
// What's the space for the input element?
|
||||
if t.width > 0 && t.width < width {
|
||||
width = t.width
|
||||
}
|
||||
if t.height > 0 && t.height < height {
|
||||
height = t.height
|
||||
}
|
||||
if width <= 0 {
|
||||
return // No space left for the text area.
|
||||
}
|
||||
|
||||
// Draw the input element if necessary.
|
||||
_, bg, _ := t.textStyle.Decompose()
|
||||
if bg != t.GetBackgroundColor() {
|
||||
for row := 0; row < height; row++ {
|
||||
for column := 0; column < width; column++ {
|
||||
screen.SetContent(x+column, y+row, ' ', nil, t.textStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide the cursor at the end.
|
||||
defer func() {
|
||||
if t.HasFocus() {
|
||||
@ -1856,6 +1975,12 @@ func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
||||
t.selectionStart = t.cursor
|
||||
newLastAction = taActionTypeSpace
|
||||
case tcell.KeyTab: // Insert a tab character. It will be rendered as TabSize spaces.
|
||||
// But forwarding takes precedence.
|
||||
if t.finished != nil {
|
||||
t.finished(key)
|
||||
return
|
||||
}
|
||||
|
||||
from, to, row := t.getSelection()
|
||||
t.cursor.pos = t.replace(from, to, "\t", t.lastAction == taActionTypeSpace)
|
||||
t.cursor.row = -1
|
||||
@ -1863,6 +1988,11 @@ func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
||||
t.findCursor(true, row)
|
||||
t.selectionStart = t.cursor
|
||||
newLastAction = taActionTypeSpace
|
||||
case tcell.KeyBacktab, tcell.KeyEscape: // Only used in forms.
|
||||
if t.finished != nil {
|
||||
t.finished(key)
|
||||
return
|
||||
}
|
||||
case tcell.KeyRune:
|
||||
if event.Modifiers()&tcell.ModAlt > 0 {
|
||||
// We accept some Alt- key combinations.
|
||||
@ -2084,7 +2214,11 @@ func (t *TextArea) MouseHandler() func(action MouseAction, event *tcell.EventMou
|
||||
}
|
||||
|
||||
// Turn mouse coordinates into text coordinates.
|
||||
column := x - rectX
|
||||
labelWidth := t.labelWidth
|
||||
if labelWidth == 0 && t.label != "" {
|
||||
labelWidth = TaggedStringWidth(t.label)
|
||||
}
|
||||
column := x - rectX - labelWidth
|
||||
row := y - rectY
|
||||
if !t.wrap {
|
||||
column += t.columnOffset
|
||||
|
Loading…
x
Reference in New Issue
Block a user