[GH-ISSUE #733] Can't tab with form centered by flex #536

Closed
opened 2026-03-04 01:05:50 +03:00 by kerem · 5 comments
Owner

Originally created by @drognisep on GitHub (Jun 10, 2022).
Original GitHub issue: https://github.com/rivo/tview/issues/733

First, I've had a wonderful time using this library. It seems very well designed and it's really easy to get going with all the documentation in the wiki. :)

The thing is, I've run into a hiccup that seems like it must be a bug. I'm trying to center a Form in a nested Flex as is shown here, and when that is done I can no longer tab through the form's fields.

I've tried a few different options, and it looks like either using Grid or not nesting at all will still allow me to tab through. Here's a way to reproduce it.

package main

import "github.com/rivo/tview"

type displayType int

const (
	NoWrap   displayType = iota
	UseFlex1             // This case is problematic
	UseFlex2             // This case is problematic too
	UseGrid
)

var display = UseFlex1

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

	// The behavior is the same whether this form is actually doing things or not.
	form := tview.NewForm()
	form.SetBorder(true)
	form.AddInputField("Field 1", "text", 0, nil, nil)
	form.AddInputField("Field 2", "text", 0, nil, nil)
	form.AddButton("OK", nil)

	var prim tview.Primitive
	switch display {
	case UseFlex1:
		prim = flexWrapper(form, 40, 9)
	case UseFlex2:
		prim = flexWrapper2(form, 40, 9)
	case UseGrid:
		prim = gridWrapper(form, 40, 9)
	case NoWrap:
		fallthrough
	default:
		prim = form
	}
	if err := app.SetRoot(prim, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}

func gridWrapper(prim tview.Primitive, width, height int) *tview.Grid {
	return tview.NewGrid().
		SetColumns(0, width, 0).
		SetRows(0, height, 0).
		AddItem(prim, 1, 1, 1, 1, 0, 0, true)
}

// Copied from the wiki
// https://github.com/rivo/tview/wiki/Modal
func flexWrapper(p tview.Primitive, width, height int) tview.Primitive {
	return tview.NewFlex().
		AddItem(nil, 0, 1, false).
		AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
			AddItem(nil, 0, 1, false).
			AddItem(p, height, 1, false).
			AddItem(nil, 0, 1, false), width, 1, false).
		AddItem(nil, 0, 1, false)
}

func flexWrapper2(p tview.Primitive, width, height int) tview.Primitive {
	return tview.NewFlex().
		AddItem(nil, 0, 1, false).
		AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
			AddItem(nil, 0, 1, false).
			AddItem(p, height, 1, true). // <-- only deviation from above is setting this focus parameter
			AddItem(nil, 0, 1, false), width, 1, false).
		AddItem(nil, 0, 1, false)
}

image

In case it's relevant, here are my environment details. I thought that maybe 1.17 was a problem, and I needed to update anyway, but this had no effect.

Key Value
Go Version (first attempt) go version go1.17.7 windows/amd64
Go Version (update attempt) go version go1.18.3 windows/amd64
tcell version github.com/gdamore/tcell/v2 v2.5.1
tview version github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
OS Windows 10
OS Version 10.0.19043 Build 19043
Originally created by @drognisep on GitHub (Jun 10, 2022). Original GitHub issue: https://github.com/rivo/tview/issues/733 First, I've had a wonderful time using this library. It seems very well designed and it's really easy to get going with all the documentation in the wiki. :) The thing is, I've run into a hiccup that seems like it must be a bug. I'm trying to center a Form in a nested Flex as is shown [here](https://github.com/rivo/tview/wiki/Modal), and when that is done I can no longer tab through the form's fields. I've tried a few different options, and it looks like either using Grid or not nesting at all will still allow me to tab through. Here's a way to reproduce it. ```golang package main import "github.com/rivo/tview" type displayType int const ( NoWrap displayType = iota UseFlex1 // This case is problematic UseFlex2 // This case is problematic too UseGrid ) var display = UseFlex1 func main() { app := tview.NewApplication() // The behavior is the same whether this form is actually doing things or not. form := tview.NewForm() form.SetBorder(true) form.AddInputField("Field 1", "text", 0, nil, nil) form.AddInputField("Field 2", "text", 0, nil, nil) form.AddButton("OK", nil) var prim tview.Primitive switch display { case UseFlex1: prim = flexWrapper(form, 40, 9) case UseFlex2: prim = flexWrapper2(form, 40, 9) case UseGrid: prim = gridWrapper(form, 40, 9) case NoWrap: fallthrough default: prim = form } if err := app.SetRoot(prim, true).EnableMouse(true).Run(); err != nil { panic(err) } } func gridWrapper(prim tview.Primitive, width, height int) *tview.Grid { return tview.NewGrid(). SetColumns(0, width, 0). SetRows(0, height, 0). AddItem(prim, 1, 1, 1, 1, 0, 0, true) } // Copied from the wiki // https://github.com/rivo/tview/wiki/Modal func flexWrapper(p tview.Primitive, width, height int) tview.Primitive { return tview.NewFlex(). AddItem(nil, 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(p, height, 1, false). AddItem(nil, 0, 1, false), width, 1, false). AddItem(nil, 0, 1, false) } func flexWrapper2(p tview.Primitive, width, height int) tview.Primitive { return tview.NewFlex(). AddItem(nil, 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(p, height, 1, true). // <-- only deviation from above is setting this focus parameter AddItem(nil, 0, 1, false), width, 1, false). AddItem(nil, 0, 1, false) } ``` ![image](https://user-images.githubusercontent.com/8890988/173002903-9e37ddbb-9f11-4ee2-b1c0-6a92b32acbff.png) In case it's relevant, here are my environment details. I thought that maybe 1.17 was a problem, and I needed to update anyway, but this had no effect. | Key | Value | | --- | --- | | Go Version (first attempt) | go version go1.17.7 windows/amd64 | | Go Version (update attempt) | go version go1.18.3 windows/amd64 | | `tcell` version | github.com/gdamore/tcell/v2 v2.5.1 | | `tview` version | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 | | OS | Windows 10 | | OS Version | 10.0.19043 Build 19043 |
kerem closed this issue 2026-03-04 01:05:50 +03:00
Author
Owner

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

The Flex version is actually two Flex's, one inside the other. The inner Flex didn't get the focus so your form also didn't. I fixed this on the Wiki page. And in your code (yes, UseFlex2 is the proper way)):

package main

import "github.com/rivo/tview"

type displayType int

const (
	NoWrap   displayType = iota
	UseFlex1             // This case is problematic
	UseFlex2             // This case is problematic too
	UseGrid
)

var display = UseFlex2

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

	// The behavior is the same whether this form is actually doing things or not.
	form := tview.NewForm()
	form.SetBorder(true)
	form.AddInputField("Field 1", "text", 0, nil, nil)
	form.AddInputField("Field 2", "text", 0, nil, nil)
	form.AddButton("OK", nil)

	var prim tview.Primitive
	switch display {
	case UseFlex1:
		prim = flexWrapper(form, 40, 9)
	case UseFlex2:
		prim = flexWrapper2(form, 40, 9)
	case UseGrid:
		prim = gridWrapper(form, 40, 9)
	case NoWrap:
		fallthrough
	default:
		prim = form
	}
	if err := app.SetRoot(prim, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}

func gridWrapper(prim tview.Primitive, width, height int) *tview.Grid {
	return tview.NewGrid().
		SetColumns(0, width, 0).
		SetRows(0, height, 0).
		AddItem(prim, 1, 1, 1, 1, 0, 0, true)
}

// Copied from the wiki
// https://github.com/rivo/tview/wiki/Modal
func flexWrapper(p tview.Primitive, width, height int) tview.Primitive {
	return tview.NewFlex().
		AddItem(nil, 0, 1, false).
		AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
			AddItem(nil, 0, 1, false).
			AddItem(p, height, 1, false).
			AddItem(nil, 0, 1, false), width, 1, true).
		AddItem(nil, 0, 1, false)
}

func flexWrapper2(p tview.Primitive, width, height int) tview.Primitive {
	return tview.NewFlex().
		AddItem(nil, 0, 1, false).
		AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
			AddItem(nil, 0, 1, false).
			AddItem(p, height, 1, true). // <-- only deviation from above is setting this focus parameter
			AddItem(nil, 0, 1, false), width, 1, true).
		AddItem(nil, 0, 1, false)
}

(I would prefer the Grid version as it is simpler. The Wiki page was created before Grid existed so it also lists the old Flex version.)

<!-- gh-comment-id:1152525813 --> @rivo commented on GitHub (Jun 10, 2022): The `Flex` version is actually two `Flex`'s, one inside the other. The inner `Flex` didn't get the focus so your form also didn't. I fixed this on the Wiki page. And in your code (yes, `UseFlex2` is the proper way)): ```go package main import "github.com/rivo/tview" type displayType int const ( NoWrap displayType = iota UseFlex1 // This case is problematic UseFlex2 // This case is problematic too UseGrid ) var display = UseFlex2 func main() { app := tview.NewApplication() // The behavior is the same whether this form is actually doing things or not. form := tview.NewForm() form.SetBorder(true) form.AddInputField("Field 1", "text", 0, nil, nil) form.AddInputField("Field 2", "text", 0, nil, nil) form.AddButton("OK", nil) var prim tview.Primitive switch display { case UseFlex1: prim = flexWrapper(form, 40, 9) case UseFlex2: prim = flexWrapper2(form, 40, 9) case UseGrid: prim = gridWrapper(form, 40, 9) case NoWrap: fallthrough default: prim = form } if err := app.SetRoot(prim, true).EnableMouse(true).Run(); err != nil { panic(err) } } func gridWrapper(prim tview.Primitive, width, height int) *tview.Grid { return tview.NewGrid(). SetColumns(0, width, 0). SetRows(0, height, 0). AddItem(prim, 1, 1, 1, 1, 0, 0, true) } // Copied from the wiki // https://github.com/rivo/tview/wiki/Modal func flexWrapper(p tview.Primitive, width, height int) tview.Primitive { return tview.NewFlex(). AddItem(nil, 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(p, height, 1, false). AddItem(nil, 0, 1, false), width, 1, true). AddItem(nil, 0, 1, false) } func flexWrapper2(p tview.Primitive, width, height int) tview.Primitive { return tview.NewFlex(). AddItem(nil, 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(p, height, 1, true). // <-- only deviation from above is setting this focus parameter AddItem(nil, 0, 1, false), width, 1, true). AddItem(nil, 0, 1, false) } ``` (I would prefer the `Grid` version as it is simpler. The Wiki page was created before `Grid` existed so it also lists the old `Flex` version.)
Author
Owner

@drognisep commented on GitHub (Jun 12, 2022):

Oh! I see. So the outer middle flex item will delegate focus to the inner middle flex item, which will delegate to the form?

And agreed, the grid version is a lot simpler to use for this case. :)

<!-- gh-comment-id:1153069508 --> @drognisep commented on GitHub (Jun 12, 2022): Oh! I see. So the outer middle flex item will delegate focus to the inner middle flex item, which will delegate to the form? And agreed, the grid version is a lot simpler to use for this case. :)
Author
Owner

@drognisep commented on GitHub (Jun 12, 2022):

Just tested locally and this is working wonderfully! Thank you! :D

<!-- gh-comment-id:1153070104 --> @drognisep commented on GitHub (Jun 12, 2022): Just tested locally and this is working wonderfully! Thank you! :D
Author
Owner

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

So the outer middle flex item will delegate focus to the inner middle flex item, which will delegate to the form?

Yes, that's correct.

<!-- gh-comment-id:1153103999 --> @rivo commented on GitHub (Jun 12, 2022): > So the outer middle flex item will delegate focus to the inner middle flex item, which will delegate to the form? Yes, that's correct.
Author
Owner

@drognisep commented on GitHub (Jun 12, 2022):

Thanks for confirming, @rivo. :)
And thanks again for putting this together. Really well done, sir. 👍

<!-- gh-comment-id:1153255638 --> @drognisep commented on GitHub (Jun 12, 2022): Thanks for confirming, @rivo. :) And thanks again for putting this together. Really well done, sir. :+1:
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#536
No description provided.