[GH-ISSUE #263] Global focus handling/cycling #202

Closed
opened 2026-03-04 01:02:57 +03:00 by kerem · 7 comments
Owner

Originally created by @urandom on GitHub (Apr 6, 2019).
Original GitHub issue: https://github.com/rivo/tview/issues/263

This is more of a question/brainstorming than an issue.

Currently there doesn't seem to be an easy way of doing global focus cycling as is seen in other UIs: pressing Tab moves the focus to the next focus candidate, and Shift-tab moves the focus to the previous one, where the candidate list is usually generated by widget placement (top-bottom/left-right, or top-bottom/right-left for RTL locales) and may or may not be overridden by developer preferences (focus indices). Are there any plans to implement this?

I'm currently trying to set this up manually by setting input captures on a per-widget basis with indication what are the previous/next ones in the hierarchy. This works sort-of-ok, however there are certain places where things break down. One of the unexpected ones I recently bumped into was the fact that other primitives may unexpectedly gain focus (such as when certain widgets are drawn), and that causes my chain to break.

As a side note, I'd probably be able to mitigate this if there was some sort of event system in place I could use to listen to. That was I'd not even need to set up a per-widget capture, but instead just listen for a focus event and act accordingly. Not sure if that's on the roadmap, though I'd be happy to work on implementing it.

Originally created by @urandom on GitHub (Apr 6, 2019). Original GitHub issue: https://github.com/rivo/tview/issues/263 This is more of a question/brainstorming than an issue. Currently there doesn't seem to be an easy way of doing global focus cycling as is seen in other UIs: pressing Tab moves the focus to the next focus candidate, and Shift-tab moves the focus to the previous one, where the candidate list is usually generated by widget placement (top-bottom/left-right, or top-bottom/right-left for RTL locales) and may or may not be overridden by developer preferences (focus indices). Are there any plans to implement this? I'm currently trying to set this up manually by setting input captures on a per-widget basis with indication what are the previous/next ones in the hierarchy. This works sort-of-ok, however there are certain places where things break down. One of the unexpected ones I recently bumped into was the fact that other primitives may unexpectedly gain focus (such as when certain widgets are drawn), and that causes my chain to break. As a side note, I'd probably be able to mitigate this if there was some sort of event system in place I could use to listen to. That was I'd not even need to set up a per-widget capture, but instead just listen for a focus event and act accordingly. Not sure if that's on the roadmap, though I'd be happy to work on implementing it.
kerem closed this issue 2026-03-04 01:02:57 +03:00
Author
Owner

@vladsol commented on GitHub (Apr 21, 2019):

So, at this moment, there is no simple solution to switch focus between items in flexbox container?

<!-- gh-comment-id:485272308 --> @vladsol commented on GitHub (Apr 21, 2019): So, at this moment, there is no simple solution to switch focus between items in flexbox container?
Author
Owner

@diamondburned commented on GitHub (Apr 22, 2019):

I think with enough poking around the Flex container and its InputHandler, you could whip something up fairly quickly. The idea is:

  • The children are kept in here
  • Flex should have its own Input handler to handle Tab
  • Shift is not caught by the library (during my tests), so it should be another key
  • There's a method to focus on the flex here, which can be modified to cycle over children instead of focusing on a single children.

I'm currently experimenting with the same type of Primitive, only with proper scrolling support (like a ScrolledWindow+Box), but that'll take me some time.

Edit1: https://github.com/gdamore/tcell/issues/95

<!-- gh-comment-id:485436173 --> @diamondburned commented on GitHub (Apr 22, 2019): I think with enough poking around the Flex container and its InputHandler, you could whip something up fairly quickly. The idea is: - The children are kept in [here](https://github.com/rivo/tview/blob/master/flex.go#L31) - Flex should have its own Input handler to handle Tab - Shift is not caught by the library (during my tests), so it should be another key - There's a method to focus on the flex [here](https://github.com/rivo/tview/blob/master/flex.go#L178), which can be modified to cycle over children instead of focusing on a single children. I'm currently experimenting with the same type of Primitive, only with proper scrolling support (like a ScrolledWindow+Box), but that'll take me some time. Edit1: https://github.com/gdamore/tcell/issues/95
Author
Owner

@rivo commented on GitHub (May 13, 2019):

You have raised multiple points which are related but not the same:

  • @urandom talks about a way to configure the order in which focus is passed on but doesn't mention any specific tview primitive.
  • @urandom also mentions that focus may get shifted when a primitive is drawn (I'd like to see this, maybe you can open an issue with an example).
  • @urandom would also like to have a way to listen to system-wide focus changes.
  • @vladsol wants to know if there is a way to switch focus in a Flex.
  • @diamondburned offers advice on how to instrument Flex to include shifting the focus.

There are a lot of things to consider but the two most relevant ones to this issue are:

  1. It is possible to nest primitives. That is, you can put a Flex into a Flex. Or a Grid into a Page which contains a Flex.
  2. When the focus is set on a container primitive (i.e. Flex, Grid, Pages, or Form), it is passed on to its contained primitive. From the point of view of the application, only a Button, InputField etc. can have focus but never a Flex or a Grid.

If you designate some kind of key combination (e.g. Tab or BackTab which is Shift-Tab) to shifting the focus, which one of your container primitives should react to it? On a Mac, for example, Cmd-Tab switches between applications, other combinations switch between windows of the same application, yet other combinations between tabs, and Tab itself between elements on those tabs/pages. We don't have this hierarchy (apps/pages/tabs/elements) in tview and we don't have many key combinations at our disposal. It is not clear where a Tab/BackTab should apply.

We also have the problem that we then may steal keyboard input from contained primitives. Many primitives already use Tab and BackTab. But if we make a Flex react to it, what happens to the primitive that uses Tab already (for example, DropDown)?

Form kind of already does this: It allows to move from form item to form item using Tab/BackTab. But this is done using the FormItem interface which must implement the SetFinishedFunc() callback so the bottom-level primitives decide themselves when it's ok to lose focus.

So I think these points need to be clarified first before any implementation starts.

<!-- gh-comment-id:491899061 --> @rivo commented on GitHub (May 13, 2019): You have raised multiple points which are related but not the same: - @urandom talks about a way to configure the order in which focus is passed on but doesn't mention any specific `tview` primitive. - @urandom also mentions that focus may get shifted when a primitive is drawn (I'd like to see this, maybe you can open an issue with an example). - @urandom would also like to have a way to listen to system-wide focus changes. - @vladsol wants to know if there is a way to switch focus in a `Flex`. - @diamondburned offers advice on how to instrument `Flex` to include shifting the focus. There are a lot of things to consider but the two most relevant ones to this issue are: 1. It is possible to nest primitives. That is, you can put a `Flex` into a `Flex`. Or a `Grid` into a `Page` which contains a `Flex`. 2. When the focus is set on a container primitive (i.e. `Flex`, `Grid`, `Pages`, or `Form`), it is passed on to its contained primitive. From the point of view of the application, only a `Button`, `InputField` etc. can have focus but never a `Flex` or a `Grid`. If you designate some kind of key combination (e.g. `Tab` or `BackTab` which is `Shift-Tab`) to shifting the focus, which one of your container primitives should react to it? On a Mac, for example, `Cmd-Tab` switches between applications, other combinations switch between windows of the same application, yet other combinations between tabs, and `Tab` itself between elements on those tabs/pages. We don't have this hierarchy (apps/pages/tabs/elements) in `tview` and we don't have many key combinations at our disposal. It is not clear where a `Tab`/`BackTab` should apply. We also have the problem that we then may steal keyboard input from contained primitives. Many primitives already use `Tab` and `BackTab`. But if we make a `Flex` react to it, what happens to the primitive that uses `Tab` already (for example, `DropDown`)? `Form` kind of already does this: It allows to move from form item to form item using `Tab`/`BackTab`. But this is done using the [`FormItem`](https://godoc.org/github.com/rivo/tview#FormItem) interface which must implement the `SetFinishedFunc()` callback so the bottom-level primitives decide themselves when it's ok to lose focus. So I think these points need to be clarified first before any implementation starts.
Author
Owner

@microo8 commented on GitHub (Jun 26, 2019):

We can take example from the web. On the web the browser knows what to do with a Tab or BackTab at all times.
When you have focus on a input and press Tab then the next input/button/link/... will get focus. Doesn't matter it it is in a div in a div in a form (in tview's it would be flex in a flex in a form).
I think it does it recursively. If you have focus in a input and press Tab the input knows that it is the only thing that can have focus, so it calls it's parents for it to select the next item to have focus. If the next item is a div, it cannot have focus (like Flex cannot have focus), so it asks it's first children to have focus. When the input was the n'th child of the div, then the div will ask it's n+1'th child. When the input was the last child of the div, the div will ask it's parent to select the next item, and so on.

For the DropDown I would suggest to change the controls to something else. And if you want some other behavior for the Tab key in other Primitives, there will be a function that explicitly sets the function to call for that Primitive.

<!-- gh-comment-id:505773041 --> @microo8 commented on GitHub (Jun 26, 2019): We can take example from the web. On the web the browser knows what to do with a `Tab` or `BackTab` at all times. When you have focus on a input and press `Tab` then the next input/button/link/... will get focus. Doesn't matter it it is in a div in a div in a form (in tview's it would be flex in a flex in a form). I think it does it recursively. If you have focus in a input and press `Tab` the input knows that it is the only thing that can have focus, so it calls it's parents for it to select the next item to have focus. If the next item is a div, it cannot have focus (like [Flex](https://godoc.org/github.com/rivo/tview#Flex) cannot have focus), so it asks it's first children to have focus. When the input was the `n'th` child of the div, then the div will ask it's `n+1'th` child. When the input was the last child of the div, the div will ask it's parent to select the next item, and so on. For the [DropDown](https://godoc.org/github.com/rivo/tview#DropDown) I would suggest to change the controls to something else. And if you want some other behavior for the `Tab` key in other Primitives, there will be a function that explicitly sets the function to call for that `Primitive`.
Author
Owner

@rivo commented on GitHub (Jun 30, 2019):

It is true that browsers know how to deal with a Tab or Backtab. I have to say, though, that I'm not happy with the browsers' handling. It is not clear at all to users where the focus goes next when they press Tab. I frequently end up on some odd link, just because the programmers didn't bother to set the tabindex correctly and the default is the result of some DOM structure only remotely related to what I see on screen. If anything, I think browsers are a bad example on how to implement Tab navigation.

In a tview Form, for example, pressing Tab continually will keep the focus within the form at all times. It could be quite frustrating if after the last element, the focus would suddenly shift to some other, possibly unrelated element on the page that happens to be next in line in a depth-first traversal.

My philosophy here is that the expected behaviour very much depends on the application. I don't see a general solution that fits everyone. tview should provide the tools to define that behaviour as needed. I'm open to suggestions on how this could be achieved.

<!-- gh-comment-id:507058117 --> @rivo commented on GitHub (Jun 30, 2019): It is true that browsers know how to deal with a `Tab` or `Backtab`. I have to say, though, that I'm not happy with the browsers' handling. It is not clear at all to users where the focus goes next when they press `Tab`. I frequently end up on some odd link, just because the programmers didn't bother to set the `tabindex` correctly and the default is the result of some DOM structure only remotely related to what I see on screen. If anything, I think browsers are a bad example on how to implement `Tab` navigation. In a `tview` `Form`, for example, pressing `Tab` continually will keep the focus within the form at all times. It could be quite frustrating if after the last element, the focus would suddenly shift to some other, possibly unrelated element on the page that happens to be next in line in a depth-first traversal. My philosophy here is that the expected behaviour very much depends on the application. I don't see a general solution that fits everyone. `tview` should provide the tools to define that behaviour as needed. I'm open to suggestions on how this could be achieved.
Author
Owner

@rivo commented on GitHub (Aug 29, 2019):

I can reopen this when there is new information.

<!-- gh-comment-id:526220370 --> @rivo commented on GitHub (Aug 29, 2019): I can reopen this when there is new information.
Author
Owner

@JCzz commented on GitHub (Feb 2, 2021):

I am unable to read:
if event.Key() == tcell.KeyUp {...

Is it related to this?

Note: I am on MacOs

<!-- gh-comment-id:771641577 --> @JCzz commented on GitHub (Feb 2, 2021): I am unable to read: `if event.Key() == tcell.KeyUp {...` Is it related to this? Note: I am on MacOs
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#202
No description provided.