[GH-ISSUE #728] Parenting API for cascading shortcut processing #532

Open
opened 2026-03-04 01:05:48 +03:00 by kerem · 11 comments
Owner

Originally created by @abitrolly on GitHub (Apr 28, 2022).
Original GitHub issue: https://github.com/rivo/tview/issues/728

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?

Originally created by @abitrolly on GitHub (Apr 28, 2022). Original GitHub issue: https://github.com/rivo/tview/issues/728 I still need [Qt Parenting System](https://wiki.qt.io/Qt_for_Beginners#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?
Author
Owner

@rivo commented on GitHub (Jun 10, 2022):

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.

<!-- gh-comment-id:1152535946 --> @rivo commented on GitHub (Jun 10, 2022): 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.
Author
Owner

@abitrolly commented on GitHub (Jun 14, 2022):

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

<!-- gh-comment-id:1154867727 --> @abitrolly commented on GitHub (Jun 14, 2022): @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](https://stackoverflow.com/help/minimal-reproducible-example) is not there yet.
Author
Owner

@rivo commented on GitHub (Nov 14, 2022):

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.

<!-- gh-comment-id:1313519863 --> @rivo commented on GitHub (Nov 14, 2022): 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.
Author
Owner

@abitrolly commented on GitHub (Nov 14, 2022):

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.

<!-- gh-comment-id:1313614447 --> @abitrolly commented on GitHub (Nov 14, 2022): 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.
Author
Owner

@rivo commented on GitHub (Nov 15, 2022):

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?

<!-- gh-comment-id:1315460231 --> @rivo commented on GitHub (Nov 15, 2022): 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?
Author
Owner

@abitrolly commented on GitHub (Nov 15, 2022):

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.

<!-- gh-comment-id:1315532974 --> @abitrolly commented on GitHub (Nov 15, 2022): The screenshot. ![image](https://user-images.githubusercontent.com/8781107/201968076-9891f10c-b878-4c25-b212-baae18126a54.png) 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.
Author
Owner

@abitrolly commented on GitHub (Nov 15, 2022):

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

<!-- gh-comment-id:1315534710 --> @abitrolly commented on GitHub (Nov 15, 2022): Oh yea, I also want to display bottom bar with these shortcuts that should change depending on current dialog.
Author
Owner

@rivo commented on GitHub (Nov 17, 2022):

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")
	}
}
<!-- gh-comment-id:1318237274 --> @rivo commented on GitHub (Nov 17, 2022): 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.) ```go 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") } } ```
Author
Owner

@abitrolly commented on GitHub (Nov 17, 2022):

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.

<!-- gh-comment-id:1318251837 --> @abitrolly commented on GitHub (Nov 17, 2022): `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.
Author
Owner

@rivo commented on GitHub (Nov 17, 2022):

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.

<!-- gh-comment-id:1318257925 --> @rivo commented on GitHub (Nov 17, 2022): > `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.
Author
Owner

@abitrolly commented on GitHub (Nov 17, 2022):

Ok. I will try it.

<!-- gh-comment-id:1318260040 --> @abitrolly commented on GitHub (Nov 17, 2022): Ok. I will try it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#532
No description provided.