rivo / tview

Terminal UI library with rich, interactive widgets — written in Golang
MIT License
10.33k stars 545 forks source link

Hiding and re-showing a page? #986

Closed xxxserxxx closed 1 month ago

xxxserxxx commented 1 month ago

Hiya. I'm trying to track down a bug in a project (an active fork of stmp) that isn't mine.

The summary is that there is a modal that can be shown exactly once. It's a modal because any input invokes a call to HidePage() on it. I've ruled out some obvious high-level stuff, like confirming the relevant functions are being called and no errors are being thrown. I've reviewed #662.

The short version is that there's a page, initially hidden, that's shown when a key is pressed via ShowPage(). Any keypress is captured by the "root" Flex of the page, and results in a call to HidePage() on the page. One interesting thing I can't account for is that, while the page is never displayed, pressing a key does get captured by the SetInputCapture() method. So the page is there and responding to input, but it's not visible. I've tried adding SendToFront() after ShowPage(), which didn't help.

The long version

In the snippets below, h.helpBook gets created and laid-out as a *tview.Flex, gets added to another *tview.Flex which then gets laid out in a grid and ends up as a *tview.Primitive and added to the app as page PageHelpBox.

Opening the modal is simple enough; helpWidget contains the "root" *tview.Flex and some other bookkeeping; a hotkey calls this code:

func (ui *Ui) ShowHelp() {
    activePage := ui.menuWidget.GetActivePage()
    ui.helpWidget.RenderHelp(activePage)

    ui.pages.ShowPage(PageHelpBox)
    ui.app.SetFocus(ui.helpModal)
}

The page is "closed" by a call to tview.Pages/HidePage(PageHelpBox).

RenderHelp() sets the help text for the visible panel in a couple of tview.TextViews, removes them from the container body via tview.Flex/Clear(), and then re-adds them with tview.Flex/AddItem(). All of the tview objects are re-used, not re-created.

As I said, this works the first time. Invoking ShowHelp() a second time, however, does nothing. It interferes with nothing in the rest of the UI, which continues to function normally -- it's just that the page is never shown again. All of the code is called; the page just never re-appears. Oh, the call to tview.Pages/ShowPage() returns the same object every time as well.

I've messed around with calling tview.Pages/SendToFront(), and tview.Pages/SwitchToPage(), and neither work. SwitchToPage() not only doesn't show the page, but just displays a blank page, which didn't really surprise me. This is the only page that's being hidden/shown this way; all other pages are activated via SwitchToPage

The only thing left that I'm suspicious of is in RenderHelp(), which is what Clear()s and re-AddItems the TextViews after changing their contents with SetText(); it does so to get a different layout of the views depending on whether both or just one have any contents:

    h.helpBook.Clear()
    if rightText != "" {
        h.helpBook.AddItem(h.leftColumn, 38, 0, false).
            AddItem(h.rightColumn, 0, 1, true) // gets focus for scrolling
    } else {
        h.helpBook.AddItem(h.leftColumn, 0, 1, false)
    }

Anyway, I'm out of ideas. Is there a call that's missing that's necessary to re-display a hidden page?

xxxserxxx commented 1 month ago

Ok, I figured it out. There is a possible bug in input handling, so I'd appreciate a read-through and response even though I'm closing this ticket.

After the page was first shown for the first time, the input capture function was still being called on every subsequent key press. I'm guessing that showing a hidden page moves it earlier in the input trap stack, and hiding it again does not change the order -- it is still processing input even though hidden. This is surprising to me: I'd expect hidden widgets to be taken out of the input handling stack, and certainly not remain at the front of it -- but I don't know if it qualifies as a bug; are hidden pages supposed to be processing input?

Here's a minimum repo that demonstrates the issue. go run . the project and press the ? key repeatedly to show the behavior. If this represents a bug, let me know and I'll file a separate ticket.

Upstream was following the "more complicated" modal tutorial, with the difference that the Pages that contain the modal are farther down the tree -- in the tutorial, they're children of the app root; in stmps, the pages containing the modal are a couple of nodes down (there are other GUI widgets -- title and status bars -- higher up). This may explain why nobody has noticed this before, if it is indeed a bug.

Note to anyone finding this: the fix, at least in my case, was to reject any unexpected Keys. I can do this because there are no key conflicts -- I may look at this more and see if I can tell if the modal is hidden and ignore the input if the modal is not visible, which would be a more general solution.