[GH-ISSUE #104] Feature: popup/docked menu #82

Closed
opened 2026-03-04 01:01:46 +03:00 by kerem · 19 comments
Owner

Originally created by @stephencheng on GitHub (Apr 17, 2018).
Original GitHub issue: https://github.com/rivo/tview/issues/104

The reason I am asking for popup menu is that the list using as menu taking too much space if laid vertically, it just doesn't look proper as a habit if laid horizontally.

Two solutions:

  • A
  1. Introduce a modal window, allow this modal window to be used as container, similar like flex. So user can add the list into it.
  2. Allow a triggering key(eg: m for menu) from current focused gui component, eg: page/table/textview etc to trigger the popup modal
  3. In this use case, user trigger 2 keys: m -> f to trigger a function call

I tried current modal, it seems the way of using it is too much mind twisted. I don't like it for a few reasons:

  1. User need to put it into a page, then hide it, you will have to switch to that page in order to show it. This way, the whole page of modal normally hide the view of your working page. It is not a popup, at least the way using it does not feel intuitive

  2. This modal page is taking a tab page and it is not naturally belongs to the tab pages. If I need one modal window as menu for a tab page for some operations, there would be too many pages used for the modal windows. To avoid displaying the modal window page, when I render the tab number and switch logic, I will have to put special logic for it

  • B

Not sure if this is good. But if it's doable and easy to implement, then it should be cool

  1. Allow a triggering key(eg: m for menu) from current focused gui component, eg: page/table/textview etc to trigger the popup modal

  2. For a tab page structured as: flex [(hidden list) | table]. The hidden list won't be showing and take any space. Only when the triggering key is pressed, then the list will be showing and take the space, the table component will take the space as normal. After user select the list item to trigger required function, the list will be hidden again.

Just a thought for your consideration. tview will get better and even cooler :)

Originally created by @stephencheng on GitHub (Apr 17, 2018). Original GitHub issue: https://github.com/rivo/tview/issues/104 The reason I am asking for popup menu is that the list using as menu taking too much space if laid vertically, it just doesn't look proper as a habit if laid horizontally. Two solutions: - A 1. Introduce a modal window, allow this modal window to be used as container, similar like flex. So user can add the list into it. 2. Allow a triggering key(eg: m for menu) from current focused gui component, eg: page/table/textview etc to trigger the popup modal 3. In this use case, user trigger 2 keys: m -> f to trigger a function call I tried current modal, it seems the way of using it is too much mind twisted. I don't like it for a few reasons: 1. User need to put it into a page, then hide it, you will have to switch to that page in order to show it. This way, the whole page of modal normally hide the view of your working page. It is not a popup, at least the way using it does not feel intuitive 2. This modal page is taking a tab page and it is not naturally belongs to the tab pages. If I need one modal window as menu for a tab page for some operations, there would be too many pages used for the modal windows. To avoid displaying the modal window page, when I render the tab number and switch logic, I will have to put special logic for it - B Not sure if this is good. But if it's doable and easy to implement, then it should be cool 1. Allow a triggering key(eg: m for menu) from current focused gui component, eg: page/table/textview etc to trigger the popup modal 2. For a tab page structured as: flex [(hidden list) | table]. The hidden list won't be showing and take any space. Only when the triggering key is pressed, then the list will be showing and take the space, the table component will take the space as normal. After user select the list item to trigger required function, the list will be hidden again. Just a thought for your consideration. tview will get better and even cooler :)
kerem closed this issue 2026-03-04 01:01:46 +03:00
Author
Owner

@muesli commented on GitHub (Apr 17, 2018):

I do something similar here: https://github.com/muesli/service-tools/blob/master/service-monitor/servicescmd.go#L90 (grep for loglevel).

<!-- gh-comment-id:382021430 --> @muesli commented on GitHub (Apr 17, 2018): I do something similar here: https://github.com/muesli/service-tools/blob/master/service-monitor/servicescmd.go#L90 (grep for loglevel).
Author
Owner

@stephencheng commented on GitHub (Apr 18, 2018):

@muesli Thanks. I did have a look of the code when you raise an issue previously, but it looks like it's tool running in linux, is it?

Is it possible you could grab a animation gif to show how your menu works? or a quick screen capture video?

<!-- gh-comment-id:382311673 --> @stephencheng commented on GitHub (Apr 18, 2018): @muesli Thanks. I did have a look of the code when you raise an issue previously, but it looks like it's tool running in linux, is it? Is it possible you could grab a animation gif to show how your menu works? or a quick screen capture video?
Author
Owner

@stephencheng commented on GitHub (Apr 18, 2018):

@muesli I managed to get it run in a debian box. It's impressive.

It works, in a way exactly as what I detailed as solution A, and as I mentioned, due to a tview's current implementation, the popup is not a popup window. It shield the foreground view of current window with the entire active page. This also brings a unacceptable bad experience of redraw the whole screen in remote ssh. Even in my local vagrant box, the refresh of the full page is terrible slow.

Also the implementation code will be too not elegant due to lack of such support with a new widget to make top most modal window with a list.

I do like the popup search input, it's a good pattern I will adopt.

Thanks

<!-- gh-comment-id:382416934 --> @stephencheng commented on GitHub (Apr 18, 2018): @muesli I managed to get it run in a debian box. It's impressive. It works, in a way exactly as what I detailed as solution A, and as I mentioned, due to a tview's current implementation, the popup is not a popup window. It shield the foreground view of current window with the entire active page. This also brings a unacceptable bad experience of redraw the whole screen in remote ssh. Even in my local vagrant box, the refresh of the full page is terrible slow. Also the implementation code will be too not elegant due to lack of such support with a new widget to make top most modal window with a list. I do like the popup search input, it's a good pattern I will adopt. Thanks
Author
Owner

@stephencheng commented on GitHub (Apr 20, 2018):

@rivo

I was considering a way for simplest support for the docked solution (B)

  1. add a property : hidden for primitive
  2. when you render (when app.Draw is triggered) the layout(eg,flex), skip adding the primitive to layout to be rendered
  3. add a exposed method SetHidden() for the component

In this way, take the demo-> presentation -> table code for example:

User could trigger a key, eg: m to show the list menu, after execution of the method, it hide the list menu again.

Just a thought

<!-- gh-comment-id:382926045 --> @stephencheng commented on GitHub (Apr 20, 2018): @rivo I was considering a way for simplest support for the docked solution (B) 1. add a property : hidden for primitive 2. when you render (when app.Draw is triggered) the layout(eg,flex), skip adding the primitive to layout to be rendered 3. add a exposed method SetHidden() for the component In this way, take the demo-> presentation -> table code for example: User could trigger a key, eg: m to show the list menu, after execution of the method, it hide the list menu again. Just a thought
Author
Owner

@rivo commented on GitHub (Apr 23, 2018):

Can we back up a little bit? I have some trouble understanding what you are trying to achieve in the first place. (And I'm sorry but I don't have the time to build and try out applications to figure it out.)

Would it be possible for you to send me a screenshot of what you need with a bit more explanation? (I can think of all kinds of "popup menus". I'm not sure what exactly you're referring to.)

Thanks!

<!-- gh-comment-id:383633585 --> @rivo commented on GitHub (Apr 23, 2018): Can we back up a little bit? I have some trouble understanding what you are trying to achieve in the first place. (And I'm sorry but I don't have the time to build and try out applications to figure it out.) Would it be possible for you to send me a screenshot of what you need with a bit more explanation? (I can think of all kinds of "popup menus". I'm not sure what exactly you're referring to.) Thanks!
Author
Owner

@stephencheng commented on GitHub (Apr 24, 2018):

@rivo

This is what I mean for the solution B:

  1. firstly, when app starts, user can see this main window page:

image

  1. when user hit a binding key: m, the menu(was hidden) could be shown(visible|rendered), lie this:

image

  1. after user select one of menu item for an execution of a method, the menu will be hidden again

The request is not really to need you to dev the app, but the capability for user to be able to do it

Let me know if you need more info

<!-- gh-comment-id:383774097 --> @stephencheng commented on GitHub (Apr 24, 2018): @rivo This is what I mean for the solution B: 1. firstly, when app starts, user can see this main window page: ![image](https://user-images.githubusercontent.com/1396675/39161155-b0ea7c72-47b2-11e8-9c1c-318189a79502.png) 2. when user hit a binding key: m, the menu(was hidden) could be shown(visible|rendered), lie this: ![image](https://user-images.githubusercontent.com/1396675/39161189-dbdb7e04-47b2-11e8-81de-eb48f8f4ee45.png) 3. after user select one of menu item for an execution of a method, the menu will be hidden again The request is not really to need you to dev the app, but the capability for user to be able to do it Let me know if you need more info
Author
Owner

@stephencheng commented on GitHub (Apr 24, 2018):

@rivo

This is the idea for the solution A:

  1. user will be presented with the main window

image

  1. when he hit the hot key: m, a modal window will pop up, provided the list as menu

image

  1. after user selected item to execute an action, the modal window(menu) will be hidden
<!-- gh-comment-id:383780774 --> @stephencheng commented on GitHub (Apr 24, 2018): @rivo This is the idea for the solution A: 1. user will be presented with the main window ![image](https://user-images.githubusercontent.com/1396675/39162396-a50b7f68-47b8-11e8-8faf-37688589c5d7.png) 2. when he hit the hot key: m, a modal window will pop up, provided the list as menu ![image](https://user-images.githubusercontent.com/1396675/39162425-c88f8092-47b8-11e8-9699-be0a975c1728.png) 3. after user selected item to execute an action, the modal window(menu) will be hidden
Author
Owner

@rivo commented on GitHub (Apr 27, 2018):

Thanks for the screenshots. Let's talk about (B) first:

Here, the menu is part of a Flex layout. But it should not be visible all the time. So is what you are missing a visible flag for a Flex item which you can switch on and off?

Regarding (A), I understand that there are some limitations to achieving this:

  1. The Modal class does not allow you to add a menu list.
  2. There is no other way to add a centered element without making the surrounding area disappear.

I am planning to let you add nil elements to Flex and Grid layouts. These would not be drawn but instead keep the background as it is. It would help you for (2), to add your list and make it centered. Let me know if this would help you achieve your goal.

In any case, however, I don't see a way around using the Pages layout. Every time there are overlapping primitives, you will have to use the Pages layout to make it work consistently.

<!-- gh-comment-id:384886595 --> @rivo commented on GitHub (Apr 27, 2018): Thanks for the screenshots. Let's talk about (B) first: Here, the menu is part of a `Flex` layout. But it should not be visible all the time. So is what you are missing a `visible` flag for a `Flex` item which you can switch on and off? Regarding (A), I understand that there are some limitations to achieving this: 1. The `Modal` class does not allow you to add a menu list. 2. There is no other way to add a centered element without making the surrounding area disappear. I am planning to let you add `nil` elements to `Flex` and `Grid` layouts. These would not be drawn but instead keep the background as it is. It would help you for (2), to add your list and make it centered. Let me know if this would help you achieve your goal. In any case, however, I don't see a way around using the `Pages` layout. Every time there are overlapping primitives, you will have to use the `Pages` layout to make it work consistently.
Author
Owner

@stephencheng commented on GitHub (Apr 27, 2018):

@rivo Thanks for the idea. Yes, I could make it work using a flex to be removed and re-added to parent flex in sequence of all other items with same parent flex.

I guess to add a SetVisible would definitely make these tedious process smoother.

Some fake code:

func (flex *Flex)SetVisible(show bool){
	if flex.hasParent{
		dad:=flex.getParent()
		sibs:= dad.kidsInOrder()
		for sib := range sibs{
			flex.Remove(sib)
		}
		if show{
			for sib := range sibs{
				dad.add(sib)
			}else{
				if sib!=flex{
				dad.add(sib)
				}
			}
		}
	}
}

By doing above, sol B could be achieved.

I also got the idea of nil element could be used for a place holder to be not drawn. However I am not sure what use case could be. It could only be used as parameter in AddItem, but can not be defined as a variable, hence be referenced.

I am guessing that what you mentioned for (A) seems to indirectly route back to sol B, am I right? I don't get it what you mean I could make the list centered, or could you elaborate it a little more?

<!-- gh-comment-id:385022146 --> @stephencheng commented on GitHub (Apr 27, 2018): @rivo Thanks for the idea. Yes, I could make it work using a flex to be removed and re-added to parent flex in sequence of all other items with same parent flex. I guess to add a SetVisible would definitely make these tedious process smoother. Some fake code: ``` func (flex *Flex)SetVisible(show bool){ if flex.hasParent{ dad:=flex.getParent() sibs:= dad.kidsInOrder() for sib := range sibs{ flex.Remove(sib) } if show{ for sib := range sibs{ dad.add(sib) }else{ if sib!=flex{ dad.add(sib) } } } } } ``` By doing above, sol B could be achieved. I also got the idea of nil element could be used for a place holder to be not drawn. However I am not sure what use case could be. It could only be used as parameter in AddItem, but can not be defined as a variable, hence be referenced. I am guessing that what you mentioned for (A) seems to indirectly route back to sol B, am I right? I don't get it what you mean I could make the list centered, or could you elaborate it a little more?
Author
Owner

@stephencheng commented on GitHub (Apr 28, 2018):

I think I have got your idea of using nil, it works but not perfect.

for example:

image

If I call the popup menu, it will show:

image

I used two nil element above flex5 and below flex5, they are just two place holders. The problem of it is that it still hide the content area: box3. I have no idea how the nil element area could help with that. Basically flex2 is the dad of box3 and flex3, you will have to swap to show one or another. Am I missing anything from you idea? is there any way that in this demo, I could still see the content area but the popup appear to be above box3?

<!-- gh-comment-id:385127621 --> @stephencheng commented on GitHub (Apr 28, 2018): I think I have got your idea of using nil, it works but not perfect. for example: ![image](https://user-images.githubusercontent.com/1396675/39390055-df24c19c-4ad2-11e8-948d-b7dbb3dd9b85.png) If I call the popup menu, it will show: ![image](https://user-images.githubusercontent.com/1396675/39390059-f4a42a4e-4ad2-11e8-9dd7-793823877782.png) I used two nil element above flex5 and below flex5, they are just two place holders. The problem of it is that it still hide the content area: box3. I have no idea how the nil element area could help with that. Basically flex2 is the dad of box3 and flex3, you will have to swap to show one or another. Am I missing anything from you idea? is there any way that in this demo, I could still see the content area but the popup appear to be above box3?
Author
Owner

@stephencheng commented on GitHub (May 2, 2018):

@rivo closing this for now.

There could be a nice to have exported function but it's all right that I could manage it in my end.

Thanks

<!-- gh-comment-id:385982812 --> @stephencheng commented on GitHub (May 2, 2018): @rivo closing this for now. There could be a nice to have exported function but it's all right that I could manage it in my end. Thanks
Author
Owner

@rivo commented on GitHub (May 2, 2018):

Apologies for replying so late. I was very busy in the last few days so I couldn't reply any sooner.

The nil option for Flex layouts was there but it wasn't fully implemented. With the latest commit, Flex and Grid don't clear their background per default anymore. So if you have nil items, other primitives below won't be erased. Using Grid to create a modal is actually even easier.

I put an example in the Wiki: https://github.com/rivo/tview/wiki/Modal

Regarding a "visible" flag, I haven't added it yet. If it's really needed, I would probably do something different, for example SetItemSize(p Primitive, fixedSize, proportion int) and say if fixedSize and proportion are 0, the item will disappear.

But if you're ok, I'll leave it the way it is for now.

<!-- gh-comment-id:386012564 --> @rivo commented on GitHub (May 2, 2018): Apologies for replying so late. I was very busy in the last few days so I couldn't reply any sooner. The `nil` option for `Flex` layouts was there but it wasn't fully implemented. With the latest commit, `Flex` and `Grid` don't clear their background per default anymore. So if you have `nil` items, other primitives below won't be erased. Using `Grid` to create a modal is actually even easier. I put an example in the Wiki: https://github.com/rivo/tview/wiki/Modal Regarding a "visible" flag, I haven't added it yet. If it's really needed, I would probably do something different, for example `SetItemSize(p Primitive, fixedSize, proportion int)` and say if `fixedSize` and `proportion` are `0`, the item will disappear. But if you're ok, I'll leave it the way it is for now.
Author
Owner

@stephencheng commented on GitHub (May 5, 2018):

@rivo

Thanks. Your new update works straight away in my demo code. However, it appears that it brought a bug in: the flex I used won't come with border and title. Is this a cost comes with the change, if so I am fine and accept that.

I only use a debug toggle to explicitly show the border and title in development env to understand the layout structure.

If this is a bug, then you might need to fix it in grid too.

Adding a SetItemSize would be definitely very useful and make tview visually more dynamic, fun to use and easy to add more client features. I (as a user) could implement my way for it, it's just a little repetitive to do same thing for multiple tview components. Again, it is a very useful thing to nice to have as builtin. I will leave the decision to you if you think it could benefit many others and also you have some time for it.

Thank you again. It's really nice work.

<!-- gh-comment-id:386807337 --> @stephencheng commented on GitHub (May 5, 2018): @rivo Thanks. Your new update works straight away in my demo code. However, it appears that it brought a bug in: the flex I used won't come with border and title. Is this a cost comes with the change, if so I am fine and accept that. I only use a debug toggle to explicitly show the border and title in development env to understand the layout structure. If this is a bug, then you might need to fix it in grid too. Adding a SetItemSize would be definitely very useful and make tview visually more dynamic, fun to use and easy to add more client features. I (as a user) could implement my way for it, it's just a little repetitive to do same thing for multiple tview components. Again, it is a very useful thing to nice to have as builtin. I will leave the decision to you if you think it could benefit many others and also you have some time for it. Thank you again. It's really nice work.
Author
Owner

@stephencheng commented on GitHub (May 8, 2018):

Something to add on as I just spent sometime to verified, the two modal examples using pages are not entirely 'correct' as the page will overwrite each other eventually if you switch one of them on.

My way of doing it is to use flex(or grid[easier]) as layout parent and manage removing all of them and add only required. This is where the visible function needed.

<!-- gh-comment-id:387431157 --> @stephencheng commented on GitHub (May 8, 2018): Something to add on as I just spent sometime to verified, the two modal examples using pages are not entirely 'correct' as the page will overwrite each other eventually if you switch one of them on. My way of doing it is to use flex(or grid[easier]) as layout parent and manage removing all of them and add only required. This is where the visible function needed.
Author
Owner

@rivo commented on GitHub (May 12, 2018):

I'm sorry but I have trouble following your explanations. It may just be a language issue so let me address what I do think I understood:

it appears that it brought a bug in: the flex I used won't come with border and title.

It never did. I haven't made any changes to this part. In fact, none of the primitives come with border and title. If you want it, you have to switch it on explicitly using SetBorder(true) and SetTitle().

the two modal examples using pages are not entirely 'correct' as the page will overwrite each other eventually if you switch one of them on.

The point of the Pages class is that you can have multiple primitives that overlap. This is not possible with Flex or Grid. Modals are typically centered on screen, with the background around it (i.e. "behind it") still visible. That's what the examples show and what you can achieve with Pages and Grid/Flex. That's your solution "A" above. I'm not sure I understand what's not "correct" about it.

<!-- gh-comment-id:388560298 --> @rivo commented on GitHub (May 12, 2018): I'm sorry but I have trouble following your explanations. It may just be a language issue so let me address what I do think I understood: > it appears that it brought a bug in: the flex I used won't come with border and title. It never did. I haven't made any changes to this part. In fact, none of the primitives come with border and title. If you want it, you have to switch it on explicitly using `SetBorder(true)` and `SetTitle()`. > the two modal examples using pages are not entirely 'correct' as the page will overwrite each other eventually if you switch one of them on. The point of the `Pages` class is that you can have multiple primitives that overlap. This is not possible with `Flex` or `Grid`. Modals are typically centered on screen, with the background around it (i.e. "behind it") still visible. That's what the examples show and what you can achieve with `Pages` and `Grid`/`Flex`. That's your solution "A" above. I'm not sure I understand what's not "correct" about it.
Author
Owner

@stephencheng commented on GitHub (May 13, 2018):

@rivo for the bug, this is what I meant:

The version I used is as below, which introduced "clearing of flex and grid back ground":

- name: github.com/rivo/tview
  version: 213c37c368f62e3a1a81f5c6dc3d2ce054d206af

With this version, even with SetBorder(true) and SetTitle("whatever"), it will not draw the border and display the title. With previous version, it did render them correctly, that's what I mean the bug is.

The code:

package main

import (
	"github.com/rivo/tview"
)

func main() {
	app := tview.NewApplication()
	flex1 := tview.NewFlex()
	flex1.SetBorder(true)
	flex1.SetTitle("flex1")

	box1 := tview.NewBox().SetBorder(true).SetTitle("box1")
	flex1.AddItem(box1, 0, 1, false)
	if err := app.SetRoot(flex1, true).Run(); err != nil {
		panic(err)
	}
}

The screen:

image

This is one version earlier before the change github.com/rivo/tview@213c37c368

image

<!-- gh-comment-id:388626132 --> @stephencheng commented on GitHub (May 13, 2018): @rivo for the bug, this is what I meant: The version I used is as below, which introduced "clearing of flex and grid back ground": ``` - name: github.com/rivo/tview version: 213c37c368f62e3a1a81f5c6dc3d2ce054d206af ``` With this version, even with SetBorder(true) and SetTitle("whatever"), it will not draw the border and display the title. With previous version, it did render them correctly, that's what I mean the bug is. The code: ``` package main import ( "github.com/rivo/tview" ) func main() { app := tview.NewApplication() flex1 := tview.NewFlex() flex1.SetBorder(true) flex1.SetTitle("flex1") box1 := tview.NewBox().SetBorder(true).SetTitle("box1") flex1.AddItem(box1, 0, 1, false) if err := app.SetRoot(flex1, true).Run(); err != nil { panic(err) } } ``` The screen: ![image](https://user-images.githubusercontent.com/1396675/39967686-8077620c-5703-11e8-85de-38fe65add3a1.png) This is one version earlier before the change https://github.com/rivo/tview/commit/213c37c368f62e3a1a81f5c6dc3d2ce054d206af ![image](https://user-images.githubusercontent.com/1396675/39967964-97ec03ca-5708-11e8-9295-4a9fe7e59e89.png)
Author
Owner

@rivo commented on GitHub (May 15, 2018):

Right. Sorry for my cluelessness. ;-) I fixed this. I had wanted to reintroduce it while I fixed the other part but forgot.

It's fixed now.

<!-- gh-comment-id:389192120 --> @rivo commented on GitHub (May 15, 2018): Right. Sorry for my cluelessness. ;-) I fixed this. I had wanted to reintroduce it while I fixed the other part but forgot. It's fixed now.
Author
Owner

@stephencheng commented on GitHub (May 16, 2018):

Thanks very much for fixing it

<!-- gh-comment-id:389556056 --> @stephencheng commented on GitHub (May 16, 2018): Thanks very much for fixing it
Author
Owner

@lenormf commented on GitHub (Jun 15, 2019):

I'm interested in the visible flag that would allow to show contextual menus in a Flex layout, is that doable already?

<!-- gh-comment-id:502379326 --> @lenormf commented on GitHub (Jun 15, 2019): I'm interested in the `visible` flag that would allow to show contextual menus in a `Flex` layout, is that doable already?
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#82
No description provided.