rivo / tview

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

Application.SetInputCapture doesn't seem to capture anything #409

Closed Abenstex closed 4 years ago

Abenstex commented 4 years ago

I setup an application with pages. When the user presses a key combination the pages should be changed. However, trying to capture the key events does not appear to do anything: `app := tview.NewApplication()

mainGrid := uiElements.BuildMainGrid(*app)

mainGrid.AddPage(uiElements.NewWelcomePanel(), "Welcome", true, true, 0)
mainGrid.AddPage(uiElements.NewTopLevelUserManagementPanel(), "Users", true, true, 1)
mainGrid.Pages.SwitchToPage("Welcome")

/*app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
    if event == tcell.NewEventKey(tcell.KeyRune, '1', tcell.ModAlt) {
        mainGrid.Pages.SwitchToPage("Users")
    } else if event == tcell.NewEventKey(tcell.KeyRune, '0', tcell.ModAlt) {
        mainGrid.Pages.SwitchToPage("Welcome")
    } 
    return event
})*/
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
    if event.Key() == tcell.KeyCtrlN {
        fmt.Fprintf(mainGrid.InfoBar, `%d ["%d"][darkcyan]%s[white][""]  `, 42, 42, "Foo")
    } else if event.Key() == tcell.KeyCtrlP {
        fmt.Fprintf(mainGrid.InfoBar, `%d ["%d"][darkcyan]%s[white][""]  `, 42, 42, "Bar")
    }
    return event
})

if err := tview.NewApplication().SetRoot(mainGrid.Grid, true).Run(); err != nil {
    panic(err)
}`

When I call mainGrid.Pages.SwitchToPage("Welcome") without waiting for key presses then the correct page is shown, though. To debug I just wanted to print something to a Textview, but that isn't working either. So my question is what am I missing or doing wrong?

tslocum commented 4 years ago

Hey @Abenstex. Did you read the relevant documentation before submitting this? It states:

TextView is a box which displays text. It implements the io.Writer interface so you can stream text to it. This does not trigger a redraw automatically but if a handler is installed via SetChangedFunc(), you can cause it to be redrawn.

Setting a handler via SetChangedFunc which calls Application.Draw whenever the TextView is changed will resolve this. You could also make any text changes first and then manually trigger an application draw by calling Application.QueueUpdateDraw with an empty function.

Abenstex commented 4 years ago

@tslocum thanks for your reply. As a matter of fact I hadn't read that part or didn't remember it. Anyway, what I did then was to include the following lines app.QueueUpdateDraw(func() { app.SetRoot(mainGrid.Grid, true) }) mainGrid.InfoBar.SetChangedFunc(func() { app.Draw() }) First I only called app.QueueUpdateDraw but since that didn't help I added mainGrid.InfoBar.SetChangedFunc. Unfortunately it still isn't captchuring any key events at least not visibly.

Abenstex commented 4 years ago

As a matter of fact I included app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { panic(errors.New("Key event: "+event.Name())) ... but the program doesn't even panic. So somehow I'm missing something essential

rivo commented 4 years ago

You're creating a variable app somewhere to which SetInputCapture() is attached. But you're not using it to run the application. You actually create another Application (with tview.NewApplication()) object when you start your session. That's why SetInputCapture() has no effect.

Here's an example that should work:

package main

import (
    "fmt"

    "github.com/gdamore/tcell"
    "github.com/rivo/tview"
)

func main() {
    pages := tview.NewPages()
    pages.AddPage("users", tview.NewBox().SetTitle("Users").SetBorder(true), true, true)
    pages.AddPage("welcome", tview.NewBox().SetTitle("Welcome").SetBorder(true), true, true)

    info := tview.NewTextView().SetDynamicColors(true).SetRegions(true)

    grid := tview.NewGrid().SetRows(0, 1)
    grid.AddItem(pages, 0, 0, 1, 1, 0, 0, true)
    grid.AddItem(info, 1, 0, 1, 1, 0, 0, false)

    app := tview.NewApplication()
    app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
        if event.Key() == tcell.KeyCtrlN {
            pages.SwitchToPage("users")
            fmt.Fprintf(info, `%d ["%d"][darkcyan]%s[white][""]  `, 42, 42, "Foo")
        } else if event.Key() == tcell.KeyCtrlP {
            pages.SwitchToPage("welcome")
            fmt.Fprintf(info, `%d ["%d"][darkcyan]%s[white][""]  `, 42, 42, "Bar")
        }
        return event
    })

    if err := app.SetRoot(grid, true).Run(); err != nil {
        panic(err)
    }
}

You also don't need to call app.Draw() as @tslocum suggested because the handler provided with the SetInputCapture() function will always be called as part of the main goroutine and a call to app.Draw() will follow implicitly. See here for more information.

Abenstex commented 4 years ago

Oh damn...sorry I really didn't see that. Sometimes you just don't see the forest for the trees.