[GH-ISSUE #426] Ability to set GetFocusable object #310

Closed
opened 2026-03-04 01:03:51 +03:00 by kerem · 4 comments
Owner

Originally created by @millerlogic on GitHub (Apr 8, 2020).
Original GitHub issue: https://github.com/rivo/tview/issues/426

Within tview, the various widgets can directly set the Box.focus which is returned by GetFocusable. But if I want to make my own 3rd party based on Box, it looks like I don't have the ability to set this focus value. Should there be Box.SetFocusable?

Example:

type Doohickey struct {
    *tview.Box
}

func NewDoohickey() *Doohickey {
    d := &Doohickey{Box: tview.NewBox()}
    d.focus = d // error
    d.SetFocusable(d) // not yet
    return d
}

An alternative would be to have a second Box constructor which allows passing in the focus object. In this case I suppose all widgets would need this extra constructor, to pass the focus object up the hierarchy. This alternative way sounds not as convenient.

Thanks

Originally created by @millerlogic on GitHub (Apr 8, 2020). Original GitHub issue: https://github.com/rivo/tview/issues/426 Within tview, the various widgets can directly set the Box.focus which is returned by GetFocusable. But if I want to make my own 3rd party based on Box, it looks like I don't have the ability to set this focus value. Should there be Box.SetFocusable? Example: ```go type Doohickey struct { *tview.Box } func NewDoohickey() *Doohickey { d := &Doohickey{Box: tview.NewBox()} d.focus = d // error d.SetFocusable(d) // not yet return d } ``` An alternative would be to have a second Box constructor which allows passing in the focus object. In this case I suppose all widgets would need this extra constructor, to pass the focus object up the hierarchy. This alternative way sounds not as convenient. Thanks
kerem closed this issue 2026-03-04 01:03:51 +03:00
Author
Owner

@millerlogic commented on GitHub (Apr 11, 2020):

Here is SetFocusable func for above if you want to fetch it, github.com/millerlogic/tview@0445361d52

However, I realized there may be a bigger problem with focus handling. What happened is I wanted a custom type based on InputField, but I eventually tracked down references to the wrong type floating around.

type CInput struct {
	*tview.InputField
}

Create and use &CInput{InputField: tview.NewInputField()}

The problem is that tview.InputField will set focus to its own type, in InputField.MouseHandler it calls setFocus(i) - this is on the InputField inner type rather than on my CInput. So this means Application.GetFocus is not referencing the correct type.

This was a quick workaround that works but is not a good long-term solution.

func (ci *CInput) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
	return func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
		// Make it so the nested type's setFocus will focus the outer type.
		return ci.InputField.MouseHandler()(action, event, func(p tview.Primitive) {
			if p == ci.InputField {
				setFocus(ci)
			} else {
				setFocus(p)
			}
		})
	}
}

It overrides the setFocus func so that calling it on the inner type will actually call it on the outer type. I would then have to do this for all functions that set focus and all derived types.

So here is a possible solution I came up with: https://github.com/rivo/tview/compare/master...millerlogic:box-self

Rather than having a focusable object, just keep a reference to the outer type's primitive. This allows the type itself to focus the correct type, check if it's focused, and so on.

I decided to make it a field rather than getter/setter functions because it's only relevant within the type itself. If external code has a Primitive reference, it should always be the correct outer type.
However it could be given a different name or use getter/setter instead.

Hopefully you understand the situation. Please let me know your thoughts, thanks

<!-- gh-comment-id:612525911 --> @millerlogic commented on GitHub (Apr 11, 2020): Here is SetFocusable func for above if you want to fetch it, https://github.com/millerlogic/tview/commit/0445361d52f010c7b028aff7838da84334d5c75b However, I realized there may be a bigger problem with focus handling. What happened is I wanted a custom type based on InputField, but I eventually tracked down references to the wrong type floating around. ```go type CInput struct { *tview.InputField } ``` Create and use ```&CInput{InputField: tview.NewInputField()}``` The problem is that tview.InputField will set focus to its own type, in InputField.MouseHandler it calls ```setFocus(i)``` - this is on the InputField inner type rather than on my CInput. So this means Application.GetFocus is not referencing the correct type. This was a quick workaround that works but is not a good long-term solution. ```go func (ci *CInput) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { return func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { // Make it so the nested type's setFocus will focus the outer type. return ci.InputField.MouseHandler()(action, event, func(p tview.Primitive) { if p == ci.InputField { setFocus(ci) } else { setFocus(p) } }) } } ``` It overrides the setFocus func so that calling it on the inner type will actually call it on the outer type. I would then have to do this for all functions that set focus and all derived types. So here is a possible solution I came up with: https://github.com/rivo/tview/compare/master...millerlogic:box-self Rather than having a focusable object, just keep a reference to the outer type's primitive. This allows the type itself to focus the correct type, check if it's focused, and so on. I decided to make it a field rather than getter/setter functions because it's only relevant within the type itself. If external code has a Primitive reference, it should always be the correct outer type. However it could be given a different name or use getter/setter instead. Hopefully you understand the situation. Please let me know your thoughts, thanks
Author
Owner

@millerlogic commented on GitHub (May 2, 2020):

Quick follow up.

I realize a field named Self might not look like a good design. But I have heard many times that user interfaces are generally the best application for OOP, and this self reference makes it that much closer to OOP style. At first I hesitated to try this option and have not done this in any other Go code, but in this case it seems like it might be a reasonable fit. It might make sense to rename the field to Primitive or something else less unfavorable.

Also if you ever do change the way key input works so that key events go down the hierarchy like mouse events do, the different widgets might want to check if they have the current focus, and this change would allow it. Otherwise, the function receiver might not match the Application.GetFocus. (or is p.GetFocusable() == app.GetFocus().GetFocusable() the solution? Even so, I'm not crazy about the way nested types would be referenced)

Finally, I can add the Focusable and GetFocusable back for backwards compatibility, it can just return the self ref.

You don't necessarily need to look at the code for this discussion, I just wanted to give it a try and remove my workaround. Feel free to let me know what's wrong with my approach or if I should be doing anything differently.

Thanks for your ongoing support for the package!

<!-- gh-comment-id:622668483 --> @millerlogic commented on GitHub (May 2, 2020): Quick follow up. I realize a field named ```Self``` might not look like a good design. But I have heard many times that user interfaces are generally the best application for OOP, and this self reference makes it that much closer to OOP style. At first I hesitated to try this option and have not done this in any other Go code, but in this case it seems like it might be a reasonable fit. It might make sense to rename the field to ```Primitive``` or something else less unfavorable. Also if you ever do change the way key input works so that key events go down the hierarchy like mouse events do, the different widgets might want to check if they have the current focus, and this change would allow it. Otherwise, the function receiver might not match the Application.GetFocus. (or is ```p.GetFocusable() == app.GetFocus().GetFocusable()``` the solution? Even so, I'm not crazy about the way nested types would be referenced) Finally, I can add the Focusable and GetFocusable back for backwards compatibility, it can just return the self ref. You don't necessarily need to look at the code for this discussion, I just wanted to give it a try and remove my workaround. Feel free to let me know what's wrong with my approach or if I should be doing anything differently. Thanks for your ongoing support for the package!
Author
Owner

@rivo commented on GitHub (Aug 18, 2020):

As for your first example, you can definitely keep another local reference in your class:

type Doohickey struct {
	*tview.Box
	focus tview.Primitive
}

func NewDoohickey() *Doohickey {
	d := &Doohickey{Box: tview.NewBox()}
	d.focus = d // no error
	return d
}

But then it requires you to also override GetFocusable(). And I realize that you may still have problems doing that because I access the focus local variable directly sometimes. I could change that so that I always call GetFocusable(). It sounds like this would solve your problem but I'm not 100% sure because I haven't spent a lot of time investigating this.

Let me know if you want to try that route.

<!-- gh-comment-id:675406608 --> @rivo commented on GitHub (Aug 18, 2020): As for your first example, you can definitely keep another local reference in your class: ```go type Doohickey struct { *tview.Box focus tview.Primitive } func NewDoohickey() *Doohickey { d := &Doohickey{Box: tview.NewBox()} d.focus = d // no error return d } ``` But then it requires you to also override `GetFocusable()`. And I realize that you may still have problems doing that because I access the `focus` local variable directly sometimes. I could change that so that I always call `GetFocusable()`. It sounds like this would solve your problem but I'm not 100% sure because I haven't spent a lot of time investigating this. Let me know if you want to try that route.
Author
Owner

@rivo commented on GitHub (Jan 11, 2021):

Please open a new issue and reference this one if you need more information.

<!-- gh-comment-id:757906451 --> @rivo commented on GitHub (Jan 11, 2021): Please open a new issue and reference this one if you need more information.
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#310
No description provided.