mirror of
https://github.com/rivo/tview.git
synced 2025-04-24 13:48:56 +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("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).
|
||||
|
42
form.go
42
form.go
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
145
textview.go
145
textview.go
@ -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…
x
Reference in New Issue
Block a user