mirror of
https://github.com/rivo/tview.git
synced 2025-05-01 22:18:30 +08:00
Text views can also become part of forms.
This commit is contained in:
parent
db36428c92
commit
3f246bda86
@ -12,6 +12,7 @@ func main() {
|
|||||||
AddInputField("First name", "", 20, nil, nil).
|
AddInputField("First name", "", 20, nil, nil).
|
||||||
AddInputField("Last name", "", 20, nil, nil).
|
AddInputField("Last name", "", 20, nil, nil).
|
||||||
AddTextArea("Address", "", 40, 0, 0, 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).
|
AddCheckbox("Age 18+", false, nil).
|
||||||
AddPasswordField("Password", "", 10, '*', nil).
|
AddPasswordField("Password", "", 10, '*', nil).
|
||||||
AddButton("Save", nil).
|
AddButton("Save", nil).
|
||||||
|
42
form.go
42
form.go
@ -38,8 +38,10 @@ type FormItem interface {
|
|||||||
|
|
||||||
// SetFinishedFunc sets the handler function for when the user finished
|
// SetFinishedFunc sets the handler function for when the user finished
|
||||||
// entering data into the item. The handler may receive events for the
|
// 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
|
// Enter key (we're done), the Escape key (cancel input), the Tab key (move
|
||||||
// next field), and the Backtab key (move to previous field).
|
// 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
|
SetFinishedFunc(handler func(key tcell.Key)) FormItem
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +90,10 @@ type Form struct {
|
|||||||
// The color of the button text.
|
// The color of the button text.
|
||||||
buttonTextColor tcell.Color
|
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.
|
// An optional function which is called when the user hits Escape.
|
||||||
cancel func()
|
cancel func()
|
||||||
}
|
}
|
||||||
@ -104,6 +110,7 @@ func NewForm() *Form {
|
|||||||
fieldTextColor: Styles.PrimaryTextColor,
|
fieldTextColor: Styles.PrimaryTextColor,
|
||||||
buttonBackgroundColor: Styles.ContrastBackgroundColor,
|
buttonBackgroundColor: Styles.ContrastBackgroundColor,
|
||||||
buttonTextColor: Styles.PrimaryTextColor,
|
buttonTextColor: Styles.PrimaryTextColor,
|
||||||
|
lastFinishedKey: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
return f
|
return f
|
||||||
@ -207,6 +214,26 @@ func (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLengt
|
|||||||
return f
|
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
|
// 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),
|
// 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
|
// 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) {
|
if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
|
||||||
f.focusedElement = 0
|
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 {
|
switch key {
|
||||||
case tcell.KeyTab, tcell.KeyEnter:
|
case tcell.KeyTab, tcell.KeyEnter:
|
||||||
f.focusedElement++
|
f.focusedElement++
|
||||||
@ -632,6 +663,11 @@ func (f *Form) Focus(delegate func(p Primitive)) {
|
|||||||
f.focusedElement = 0
|
f.focusedElement = 0
|
||||||
f.Focus(delegate)
|
f.Focus(delegate)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
if key < 0 && f.lastFinishedKey >= 0 {
|
||||||
|
// Repeat the last action.
|
||||||
|
handler(f.lastFinishedKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
145
textview.go
145
textview.go
@ -142,6 +142,10 @@ type TextView struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
*Box
|
*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.
|
// The text buffer.
|
||||||
buffer []string
|
buffer []string
|
||||||
|
|
||||||
@ -152,6 +156,15 @@ type TextView struct {
|
|||||||
// to be re-indexed.
|
// to be re-indexed.
|
||||||
index []*textViewIndex
|
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.
|
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.
|
||||||
align int
|
align int
|
||||||
|
|
||||||
@ -206,8 +219,9 @@ type TextView struct {
|
|||||||
// after punctuation characters.
|
// after punctuation characters.
|
||||||
wordWrap bool
|
wordWrap bool
|
||||||
|
|
||||||
// The (starting) color of the text.
|
// The (starting) style of the text. This also defines the background color
|
||||||
textColor tcell.Color
|
// of the main text element.
|
||||||
|
textStyle tcell.Style
|
||||||
|
|
||||||
// If set to true, the text color can be changed dynamically by piping color
|
// If set to true, the text color can be changed dynamically by piping color
|
||||||
// strings in square brackets to the text view.
|
// 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
|
// An optional function which is called when one or more regions were
|
||||||
// highlighted.
|
// highlighted.
|
||||||
highlighted func(added, removed, remaining []string)
|
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.
|
// NewTextView returns a new text view.
|
||||||
func NewTextView() *TextView {
|
func NewTextView() *TextView {
|
||||||
return &TextView{
|
return &TextView{
|
||||||
Box: NewBox(),
|
Box: NewBox(),
|
||||||
|
labelStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),
|
||||||
highlights: make(map[string]struct{}),
|
highlights: make(map[string]struct{}),
|
||||||
lineOffset: -1,
|
lineOffset: -1,
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
align: AlignLeft,
|
align: AlignLeft,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
textColor: Styles.PrimaryTextColor,
|
textStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),
|
||||||
regions: false,
|
regions: false,
|
||||||
dynamicColors: 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
|
// 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,
|
// scrollable. If true, text is kept in a buffer and can be navigated. If false,
|
||||||
// the last line will always be visible.
|
// 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
|
// dynamically by sending color strings in square brackets to the text view if
|
||||||
// dynamic colors are enabled).
|
// dynamic colors are enabled).
|
||||||
func (t *TextView) SetTextColor(color tcell.Color) *TextView {
|
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
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +493,22 @@ func (t *TextView) SetHighlightedFunc(handler func(added, removed, remaining []s
|
|||||||
return t
|
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).
|
// ScrollTo scrolls to the specified row and column (both starting with 0).
|
||||||
func (t *TextView) ScrollTo(row, column int) *TextView {
|
func (t *TextView) ScrollTo(row, column int) *TextView {
|
||||||
if !t.scrollable {
|
if !t.scrollable {
|
||||||
@ -668,6 +750,13 @@ func (t *TextView) Focus(delegate func(p Primitive)) {
|
|||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
t.Box.Focus(delegate)
|
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.
|
// 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.Box.DrawForSubclass(screen, t)
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
totalWidth, totalHeight := screen.Size()
|
|
||||||
|
|
||||||
// Get the available size.
|
// Get the available size.
|
||||||
x, y, width, height := t.GetInnerRect()
|
x, y, width, height := t.GetInnerRect()
|
||||||
t.pageSize = height
|
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 the width has changed, we need to reindex.
|
||||||
if width != t.lastWidth && t.wrap {
|
if width != t.lastWidth && t.wrap {
|
||||||
t.index = nil
|
t.index = nil
|
||||||
@ -1101,10 +1226,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the buffer.
|
// Draw the buffer.
|
||||||
defaultStyle := tcell.StyleDefault.Foreground(t.textColor).Background(t.backgroundColor)
|
|
||||||
for line := t.lineOffset; line < len(t.index); line++ {
|
for line := t.lineOffset; line < len(t.index); line++ {
|
||||||
// Are we done?
|
// Are we done?
|
||||||
if line-t.lineOffset >= height || y+line-t.lineOffset >= totalHeight {
|
if line-t.lineOffset >= height {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1193,7 +1317,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mix the existing style with the new style.
|
// 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?
|
// Do we highlight this character?
|
||||||
var highlighted bool
|
var highlighted bool
|
||||||
@ -1224,7 +1348,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop at the right border.
|
// Stop at the right border.
|
||||||
if posX+screenWidth > width || x+posX >= totalWidth {
|
if posX+screenWidth > width {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1266,6 +1390,9 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||||||
if t.done != nil {
|
if t.done != nil {
|
||||||
t.done(key)
|
t.done(key)
|
||||||
}
|
}
|
||||||
|
if t.finished != nil {
|
||||||
|
t.finished(key)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user