tview
tview copied to clipboard
How can I show an InputField at the position of the current list item?
Hi,
I'm building something like this:
package main
import (
"strconv"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func main() {
app := tview.NewApplication()
button := tview.NewButton("+ New chat")
button.SetBorder(true)
list := tview.NewList()
list.SetTitle("History").SetBorder(true)
for i := 0; i < 50; i++ {
list.AddItem("item "+strconv.FormatInt(int64(i), 10), "", rune(0), nil)
}
list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'j':
if list.GetCurrentItem() < list.GetItemCount() {
list.SetCurrentItem(list.GetCurrentItem() + 1)
}
case 'k':
if list.GetCurrentItem() > 0 {
list.SetCurrentItem(list.GetCurrentItem() - 1)
}
}
return event
})
textView := tview.NewTextView()
textView.SetTitle("Conversation").SetBorder(true)
textArea := tview.NewTextArea()
textArea.SetTitle("Question").SetBorder(true)
mainFlex := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(button, 3, 1, false).
AddItem(list, 0, 1, false), 0, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(textView, 0, 1, false).
AddItem(textArea, 5, 1, false), 0, 3, false), 0, 1, false)
if err := app.SetRoot(mainFlex, true).SetFocus(list).Run(); err != nil {
panic(err)
}
}
I want to allow to edit the list item: for e.g, when user press e
, I want to show an InputField at the position of the list item. To do that, I can create a Modal on top of this layout, will all items nil except for the one at the list item's position:
modal := func(p tview.Primitive, currentIndex int) tview.Primitive {
return tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(nil, 4+(currentIndex*2), 1, false).
AddItem(p, 1, 1, true).
AddItem(nil, 0, 1, false), 0, 1, true).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(nil, 0, 1, false).
AddItem(nil, 5, 1, false), 0, 3, false), 0, 1, true)
}
and then I can call it in the SetInputCapture
func:
list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
case 'e':
pages.AddPage(pageEditTitle, modal(editTitleInputField, list.GetCurrentItem()), true, false)
https://gist.github.com/quantonganh/88d14214cffcf226cfcd7bea5a17ecf3
If the list is short, it will be ok. But if the current index is greater than the height of the screen / 2 (there is a blank line between each list item), it shows nothing as it is out of the screen.
Let me give you an example:

Please look at the above screenshot: if I scroll down, then up to hide some top items, then I stop at the item that has index 5 and press e
, the InputField will be shown at the position of "item 8".
So, how can I show an InputField at the position of the current list item?
If I can get the position of the "item 5" on the screen (2 in this case, if we start from 0 - "item 3", 1 - "item 4"), then I can show the InputField at line: 4 + 2*2 = 8 (and it will be matched, since the "New chat" button has height of 3, the "item 5" is at line 5).
I solved it by counting how many items are hidden base on the height of list:
var hiddenItemCount int
list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
_, _, _, height := list.GetInnerRect()
switch event.Rune() {
case 'j':
if list.GetCurrentItem() < list.GetItemCount() {
list.SetCurrentItem(list.GetCurrentItem() + 1)
}
if list.GetCurrentItem() >= height/2 {
hiddenItemCount = list.GetCurrentItem() + 1 - (height / 2)
}
case 'k':
if list.GetCurrentItem() > 0 {
list.SetCurrentItem(list.GetCurrentItem() - 1)
}
if list.GetCurrentItem()+1 == hiddenItemCount {
hiddenItemCount--
}
case 'e':
pages.AddPage(pageEditTitle, modal(editTitleInputField, list.GetCurrentItem()-hiddenItemCount), true, false)
This is probably more complicated when the list is longer than the screen height and there is scrolling involved.
There is currently no solution to this. Eventually, I might add a function to return the current selection's screen position. It will be needed in Table
, TreeView
, and others as well.
But for now, there is nothing like this in tview
.