tview icon indicating copy to clipboard operation
tview copied to clipboard

How can I show an InputField at the position of the current list item?

Open quantonganh opened this issue 1 year ago • 2 comments

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:

Screen Shot 2023-04-08 at 9 21 07 PM

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).

quantonganh avatar Apr 08 '23 14:04 quantonganh

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)

quantonganh avatar Apr 11 '23 01:04 quantonganh

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.

rivo avatar Jun 18 '23 11:06 rivo