rivo / tview

Terminal UI library with rich, interactive widgets — written in Golang
MIT License
11.17k stars 575 forks source link

Windows Non-Stop Flickering when calling function QueueUpdateDraw() inside SetAfterDrawFunc() #909

Closed Vinrithi closed 1 year ago

Vinrithi commented 1 year ago

Hello. I would like to appreciate you for the wonderful work you've done in providing this library. I had started using it in 2022 in a small project and noticed there were new updates. However, there seems to be a bug which causes the application to flicker non-stop in Windows in the latest release v0.0.0-20231031172508-2dfe06011790. I updated from v0.0.0-20220812085834-0e6b21a48e96. I've created a sample code below to showcase this. screen-capture.webm

The above video shows what is happening in the UI and this is the code:

func main() {

    app := tview.NewApplication()
    form := tview.NewForm().
        AddDropDown("Title", []string{"Mr.", "Ms.", "Mrs.", "Dr.", "Prof."}, 0, nil).
        AddInputField("First name", "", 20, nil, nil).
        AddInputField("Last name", "", 20, nil, nil).
        AddCheckbox("Age 18+", false, nil).
        AddPasswordField("Password", "", 10, '*', nil).
        AddButton("Save", nil).
        AddButton("Quit", func() {
            app.Stop()
        })
    form.SetBorder(true).SetTitle("Enter some data").SetTitleAlign(tview.AlignLeft)

    app.SetAfterDrawFunc(func(screen tcell.Screen) {
        go func() {
            app.QueueUpdateDraw(func() {

            })
        }()
    })

    if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
        panic(err)
    }
}

This only happens when I call

app.SetAfterDrawFunc(func(screen tcell.Screen) {
        go func() {
            app.QueueUpdateDraw(func() {

            })
        }()
    })

and it only happens in Windows. I've tried in Command Prompt and Powershell latest 7.3.9 and the output is the same. In Linux there is no issue whatsoever. The application is running as expected. In my actual project, Before the latest updates, the flickering was not happening:

go.mod before

go 1.19

require (
    github.com/briandowns/spinner v1.23.0
    github.com/epiclabs-io/winman v0.0.0-20220901164457-3d8c4b3ae090
    github.com/gdamore/tcell/v2 v2.6.0
    github.com/rivo/tview v0.0.0-20220812085834-0e6b21a48e96
    golang.design/x/clipboard v0.7.0
)

require (
    github.com/fatih/color v1.15.0 // indirect
    github.com/gdamore/encoding v1.0.0 // indirect
    github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
    github.com/mattn/go-colorable v0.1.13 // indirect
    github.com/mattn/go-isatty v0.0.17 // indirect
    github.com/mattn/go-runewidth v0.0.14 // indirect
    github.com/rivo/uniseg v0.4.3 // indirect
    golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
    golang.org/x/image v0.6.0 // indirect
    golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
    golang.org/x/sys v0.6.0 // indirect
    golang.org/x/term v0.5.0 // indirect
    golang.org/x/text v0.8.0 // indirect
)

go.mod updated

go 1.19

require (
    github.com/briandowns/spinner v1.23.0
    github.com/epiclabs-io/winman v0.0.0-20220901164457-3d8c4b3ae090
    github.com/gdamore/tcell/v2 v2.6.0
    github.com/rivo/tview v0.0.0-20231031172508-2dfe06011790
    golang.design/x/clipboard v0.7.0
)

require (
    github.com/fatih/color v1.15.0 // indirect
    github.com/gdamore/encoding v1.0.0 // indirect
    github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
    github.com/mattn/go-colorable v0.1.13 // indirect
    github.com/mattn/go-isatty v0.0.20 // indirect
    github.com/mattn/go-runewidth v0.0.15 // indirect
    github.com/rivo/uniseg v0.4.4 // indirect
    golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
    golang.org/x/exp/shiny v0.0.0-20231006140011-7918f672742d // indirect
    golang.org/x/image v0.13.0 // indirect
    golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe // indirect
    golang.org/x/sys v0.13.0 // indirect
    golang.org/x/term v0.13.0 // indirect
    golang.org/x/text v0.13.0 // indirect
)

In my actual project, I need to call the function to redraw the UI for smooth operations. Otherwise, I'm forced to move the terminal a bit for it to refresh UI as shown in the videos below (I have used Ubuntu Terminal since it has no flickering):

screen-capture-without-queue-update-draw.webm screen-capture-with-queue-update-draw.webm

And here's the project on Windows:

windows-flickering.webm

I have no problem running my application in Ubuntu, but I really wanted it to work in Windows as well since that's where it's being used the most. Kindly have a look and advise.

rivo commented 1 year ago

It is necessary to clear the screen before each call to Draw() (see https://github.com/rivo/tview/issues/899#issuecomment-1777112223). This should generally not be a problem. But in your particular example, you are creating an endless loop of calls to Draw(): In your SetAfterDrawFunc() callback, you're scheduling another Draw() invocation. And then again and again.

I'm not sure why you are doing that, but even if there was no flickering, it would keep the CPU unnecessarily busy. I'm guessing that there is a slight difference in terminals on Windows and on other OSes but it happens on non-Windows OSes too, it's just not visible.

Maybe you want to clarify why you create an endless Draw() cycle?

Vinrithi commented 1 year ago

Thanks for the clarification on what is happening. I've relooked at my code and I've seen I might have been messing up by calling

app.SetAfterDrawFunc(func(screen tcell.Screen) {
        go func() {
            app.QueueUpdateDraw(func() {

            })
        }()
    })

I had some windows like alerts and progress bars made using the winman library, and was having difficulties showing and hiding them, until I encountered the function above. It was solving the issues I had, but didn't know the repercussions in things like CPU usage. I've reimplemented using Draw() and the program is running well on both Windows and Linux with minimal CPU usage.