Text views can also become part of forms.

pull/294/merge
Oliver 2 years ago
parent db36428c92
commit 3f246bda86

@ -12,6 +12,7 @@ func main() {
AddInputField("First name", "", 20, nil, nil).
AddInputField("Last name", "", 20, nil, nil).
AddTextArea("Address", "", 40, 0, 0, nil).
AddTextView("Notes", "This is just a demo.\nYou can enter whatever you wish.", 40, 2, true, false).
AddCheckbox("Age 18+", false, nil).
AddPasswordField("Password", "", 10, '*', nil).
AddButton("Save", nil).

@ -38,8 +38,10 @@ type FormItem interface {
// 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
// next field), and the Backtab key (move to previous field).
// Enter key (we're done), the Escape key (cancel input), the Tab key (move
// to next field), the Backtab key (move to previous field), or a negative
// value, indicating that the action for the last known key should be
// repeated.
SetFinishedFunc(handler func(key tcell.Key)) FormItem
}
@ -88,6 +90,10 @@ type Form struct {
// The color of the button text.
buttonTextColor tcell.Color
// The last (valid) key that wsa sent to a "finished" handler or -1 if no
// such key is known yet.
lastFinishedKey tcell.Key
// An optional function which is called when the user hits Escape.
cancel func()
}
@ -104,6 +110,7 @@ func NewForm() *Form {
fieldTextColor: Styles.PrimaryTextColor,
buttonBackgroundColor: Styles.ContrastBackgroundColor,
buttonTextColor: Styles.PrimaryTextColor,
lastFinishedKey: -1,
}
return f
@ -207,6 +214,26 @@ func (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLengt
return f
}
// AddTextView adds a text view to the form. It has a label and text, a size
// (width and height) referring to the actual text element (a fieldWidth of 0
// extends it as far right as possible, a fieldHeight of 0 will cause it to be
// [DefaultFormFieldHeight]), a flag to turn on/off dynamic colors, and a flag
// to turn on/off scrolling. If scrolling is turned off, the text view will not
// receive focus.
func (f *Form) AddTextView(label, text string, fieldWidth, fieldHeight int, dynamicColors, scrollable bool) *Form {
if fieldHeight == 0 {
fieldHeight = DefaultFormFieldHeight
}
textArea := NewTextView().
SetLabel(label).
SetSize(fieldHeight, fieldWidth).
SetDynamicColors(dynamicColors).
SetScrollable(scrollable).
SetText(text)
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
@ -614,7 +641,11 @@ func (f *Form) Focus(delegate func(p Primitive)) {
if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
f.focusedElement = 0
}
handler := func(key tcell.Key) {
var handler func(key tcell.Key)
handler = func(key tcell.Key) {
if key >= 0 {
f.lastFinishedKey = key
}
switch key {
case tcell.KeyTab, tcell.KeyEnter:
f.focusedElement++
@ -632,6 +663,11 @@ func (f *Form) Focus(delegate func(p Primitive)) {
f.focusedElement = 0
f.Focus(delegate)
}
default:
if key < 0 && f.lastFinishedKey >= 0 {
// Repeat the last action.
handler(f.lastFinishedKey)
}
}
}

@ -142,6 +142,10 @@ type TextView struct {
sync.Mutex
*Box
// The size of the text area. If set to 0, the text view will use the entire
// available space.
width, height int
// The text buffer.
buffer []string
@ -152,6 +156,15 @@ type TextView struct {
// to be re-indexed.
index []*textViewIndex
// The label text shown, usually when part of a form.
label string
// The width of the text area's label.
labelWidth int
// The label style.
labelStyle tcell.Style
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.
align int
@ -206,8 +219,9 @@ type TextView struct {
// after punctuation characters.
wordWrap bool
// The (starting) color of the text.
textColor tcell.Color
// The (starting) style of the text. This also defines the background color
// of the main text element.
textStyle tcell.Style
// If set to true, the text color can be changed dynamically by piping color
// strings in square brackets to the text view.
@ -235,23 +249,66 @@ type TextView struct {
// An optional function which is called when one or more regions were
// highlighted.
highlighted func(added, removed, remaining []string)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
}
// NewTextView returns a new text view.
func NewTextView() *TextView {
return &TextView{
Box: NewBox(),
labelStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),
highlights: make(map[string]struct{}),
lineOffset: -1,
scrollable: true,
align: AlignLeft,
wrap: true,
textColor: Styles.PrimaryTextColor,
textStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),
regions: false,
dynamicColors: false,
}
}
// SetLabel sets the text to be displayed before the text view.
func (t *TextView) SetLabel(label string) *TextView {
t.label = label
return t
}
// GetLabel returns the text to be displayed before the text view.
func (t *TextView) 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 *TextView) SetLabelWidth(width int) *TextView {
t.labelWidth = width
return t
}
// SetSize sets the screen size of the main text element of the text view. This
// 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 *TextView) SetSize(rows, columns int) *TextView {
t.width = columns
t.height = rows
return t
}
// GetFieldWidth returns this primitive's field width.
func (t *TextView) GetFieldWidth() int {
return t.width
}
// GetFieldHeight returns this primitive's field height.
func (t *TextView) GetFieldHeight() int {
return t.height
}
// 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.
@ -315,7 +372,16 @@ func (t *TextView) SetTextAlign(align int) *TextView {
// dynamically by sending color strings in square brackets to the text view if
// dynamic colors are enabled).
func (t *TextView) SetTextColor(color tcell.Color) *TextView {
t.textColor = color
t.textStyle = t.textStyle.Foreground(color)
return t
}
// SetTextStyle sets the initial style of the text (which can be changed
// dynamically by sending color strings in square brackets to the text view if
// dynamic colors are enabled). This style's background color also determines
// the background color of the main text element (even if empty).
func (t *TextView) SetTextStyle(style tcell.Style) *TextView {
t.textStyle = style
return t
}
@ -427,6 +493,22 @@ func (t *TextView) SetHighlightedFunc(handler func(added, removed, remaining []s
return t
}
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
func (t *TextView) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
t.finished = handler
return t
}
// SetFormAttributes sets attributes shared by all form items.
func (t *TextView) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
t.labelWidth = labelWidth
t.backgroundColor = bgColor
t.labelStyle = t.labelStyle.Foreground(labelColor)
// We ignore the field background color because this is a read-only element.
t.textStyle = tcell.StyleDefault.Foreground(fieldTextColor).Background(bgColor)
return t
}
// ScrollTo scrolls to the specified row and column (both starting with 0).
func (t *TextView) ScrollTo(row, column int) *TextView {
if !t.scrollable {
@ -668,6 +750,13 @@ func (t *TextView) Focus(delegate func(p Primitive)) {
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.
if t.finished != nil && !t.scrollable {
t.finished(-1)
return
}
}
// HasFocus returns whether or not this primitive has focus.
@ -1014,12 +1103,48 @@ func (t *TextView) Draw(screen tcell.Screen) {
t.Box.DrawForSubclass(screen, t)
t.Lock()
defer t.Unlock()
totalWidth, totalHeight := screen.Size()
// Get the available size.
x, y, width, height := t.GetInnerRect()
t.pageSize = height
// 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 text 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 text 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)
}
}
}
// If the width has changed, we need to reindex.
if width != t.lastWidth && t.wrap {
t.index = nil
@ -1101,10 +1226,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// Draw the buffer.
defaultStyle := tcell.StyleDefault.Foreground(t.textColor).Background(t.backgroundColor)
for line := t.lineOffset; line < len(t.index); line++ {
// Are we done?
if line-t.lineOffset >= height || y+line-t.lineOffset >= totalHeight {
if line-t.lineOffset >= height {
break
}
@ -1193,7 +1317,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// Mix the existing style with the new style.
style := overlayStyle(defaultStyle, foregroundColor, backgroundColor, attributes)
style := overlayStyle(t.textStyle, foregroundColor, backgroundColor, attributes)
// Do we highlight this character?
var highlighted bool
@ -1224,7 +1348,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// Stop at the right border.
if posX+screenWidth > width || x+posX >= totalWidth {
if posX+screenWidth > width {
return true
}
@ -1266,6 +1390,9 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
if t.done != nil {
t.done(key)
}
if t.finished != nil {
t.finished(key)
}
return
}

Loading…
Cancel
Save