rivo / tview

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

Parenting API for cascading shortcut processing #728

Open abitrolly opened 2 years ago

abitrolly commented 2 years ago

I still need Qt Parenting System for blocking modal in #662 to start shortcut processing from the topmost widget and then forward unhandled shortcuts to parents. The problem is that tview doesn't have this universal parent relationship.

Would it be good to add SetParent/GetParent to Box primitive and let methods like Pages.AddPage set it?

rivo commented 2 years ago

References are top-down in tview only for various reasons. (And maintaining both directions is a nightmare.) I don't see a parent relationship coming to tview any time soon. I thought we had solved your #662 issue. If there's still something open, we can work to find a solution.

abitrolly commented 2 years ago

@rivo yep, #662 is not solved, because there were no diagrams to explain how the event system works, what it is called top-down and why app and root is at the top. Now that I've moved through the code, the main complain is that I don't want to hardcode widget references and shortcuts inside its parent widgets.

I my head widgets form dynamic stack of windows, where top window on the screen is getting focus and can choose either to process a key, or to forward it down the stack. Again I think that the modal help screen that is invoked by F1 should be able to work from any place in app, and take a notion of current context. I was almost able to do it with pages, but such MRE is not there yet.

rivo commented 1 year ago

From what I can tell, #662 was resolved. There were no further open questions. We can reopen it if you need help achieving some specific objective.

I understand that you have a different mental model of how a GUI library should be organized. Maybe this is coming from Qt/Pyside2 which you mentioned? I don't know. But tview is not designed in that way and it's unlikely that it will be completely restructured to fit a different architectural philosophy.

If you have a specific problem, something that cannot be done at the moment, please describe it in detail so it can be reproduced, and I will give my best to provide a solution.

abitrolly commented 1 year ago

Ok, the problem. I need a parent widget to forward shortcut to child widget if the child widget overrides this shortcut, without explicitly coding all child shortcuts into parent widget.

rivo commented 1 year ago

This is a description of a general solution rather than a specific problem description. What I meant was for you to describe what the user does or wants to do in your application and how that can currently not be achieved. Maybe you want to include a screenshot, if that helps?

abitrolly commented 1 year ago

The screenshot.

image

From here https://gitlab.com/abitrolly/dnf-go-gui

I want to show popup with package description if pressed I or ENTER. Help for the current widget if pressed F1. ESC to close current dialog.

abitrolly commented 1 year ago

Oh yea, I also want to display bottom bar with these shortcuts that should change depending on current dialog.

rivo commented 1 year ago

Lots of ways to do this. Here's some example code. Adding the bottom bar is "left as an exercise to the reader". (Check out the presentation demo in the repo for ideas.)

package main

import (
    "github.com/gdamore/tcell/v2"
    "github.com/rivo/tview"
)

var helpTexts = map[string]string{
    "updates": "Help text for update table",
    "info":    "Help text for info text view",
}

func main() {
    app := tview.NewApplication()

    pages := tview.NewPages()
    updates(pages)
    info(pages)
    showHelp := help(pages)

    app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
        if event.Key() == tcell.KeyF1 {
            page, _ := pages.GetFrontPage()
            text, ok := helpTexts[page]
            if ok {
                showHelp(text)
            }
            return nil
        }
        return event
    })

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

func updates(pages *tview.Pages) {
    table := tview.NewTable().
        SetSelectable(true, false).
        SetCellSimple(0, 0, "update1").
        SetCellSimple(1, 0, "update2").
        SetSelectedFunc(func(row, column int) {
            pages.SwitchToPage("info")
        })
    table.SetBorder(true).SetTitle("Updates")
    pages.AddPage("updates", table, true, true)
}

func info(pages *tview.Pages) {
    text := tview.NewTextView().
        SetText("Some information here...")
    text.SetBorder(true).
        SetTitle("Info").
        SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
            if event.Key() == tcell.KeyEnter {
                pages.SwitchToPage("updates")
                return nil
            } else {
                return event
            }
        })
    pages.AddPage("info", text, true, false)
}

func help(pages *tview.Pages) func(text string) {
    textView := tview.NewTextView()
    textView.SetBorder(true).
        SetTitle("Help").
        SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
            if event.Key() == tcell.KeyEscape {
                pages.HidePage("help")
                return nil
            } else {
                return event
            }
        })
    pages.AddPage("help", tview.NewGrid().
        SetRows(0, 8, 0).
        SetColumns(0, 80, 0).
        AddItem(textView, 1, 1, 1, 1, 0, 0, true), true, false)
    return func(text string) {
        textView.SetText(text)
        pages.ShowPage("help")
    }
}
abitrolly commented 1 year ago

info and help information should be popups with package listing always visible on the background. And in case of help page for info, help should be rendered on top, so that when the help is discarded, the info became active again. Also I don't want to hardcode return path from info dialog into updates page. Like info page should know nothing about who called it. It should just receive parameter of what package to show.

rivo commented 1 year ago

info and help information should be popups with package listing always visible on the background.

Sure. Use ShowPage() instead of SwitchToPage() for the info page. This should also result in your other requirements as Pages is basically a deck of cards. If you hide the top card, the one below it will automatically become the top card again.

I'm not going to code this for you. It's all there and it's all possible. You just need to dive into the code a little.

abitrolly commented 1 year ago

Ok. I will try it.