[GH-ISSUE #197] Timer Example #155

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

Originally created by @lnxbil on GitHub (Nov 27, 2018).
Original GitHub issue: https://github.com/rivo/tview/issues/197

Hi Oliver,

I'm currently researching and trying different tui libraries for GO and wanted a MWE for a timer based implemention with tview and want to ask if you'd be willing to include it your demos repository.

I based it on your modal example and already created everything for pull request but wanted to ask first as suggested by your CONTRIBUTING.md:

screenshot

Code is

// Demo code for a timer based update
package main

import (
	"fmt"
	"time"

	"github.com/rivo/tview"
)

var view *tview.Modal
var app *tview.Application

var refreshInterval = 500 * time.Millisecond

func currentTimeString() string {
	t := time.Now()
	return fmt.Sprintf(t.Format("Current time is 15:04:05"))
}

func updateTime() {
	time.Sleep(refreshInterval)
	for {
		view.SetText(currentTimeString())
		app.Draw()
		time.Sleep(refreshInterval)
	}
}

func main() {
	app = tview.NewApplication()
	view = tview.NewModal().
		SetText(currentTimeString()).
		AddButtons([]string{"Quit", "Cancel"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			if buttonLabel == "Quit" {
				app.Stop()
			}
		})

	go updateTime()
	if err := app.SetRoot(view, false).Run(); err != nil {
		panic(err)
	}
}

Originally created by @lnxbil on GitHub (Nov 27, 2018). Original GitHub issue: https://github.com/rivo/tview/issues/197 Hi Oliver, I'm currently researching and trying different tui libraries for GO and wanted a MWE for a timer based implemention with `tview` and want to ask if you'd be willing to include it your `demos` repository. I based it on your modal example and already created everything for pull request but wanted to ask first as suggested by your `CONTRIBUTING.md`: ![screenshot](https://user-images.githubusercontent.com/727711/49082323-c6676780-f249-11e8-8b32-88f3297e20e7.png) Code is ```go // Demo code for a timer based update package main import ( "fmt" "time" "github.com/rivo/tview" ) var view *tview.Modal var app *tview.Application var refreshInterval = 500 * time.Millisecond func currentTimeString() string { t := time.Now() return fmt.Sprintf(t.Format("Current time is 15:04:05")) } func updateTime() { time.Sleep(refreshInterval) for { view.SetText(currentTimeString()) app.Draw() time.Sleep(refreshInterval) } } func main() { app = tview.NewApplication() view = tview.NewModal(). SetText(currentTimeString()). AddButtons([]string{"Quit", "Cancel"}). SetDoneFunc(func(buttonIndex int, buttonLabel string) { if buttonLabel == "Quit" { app.Stop() } }) go updateTime() if err := app.SetRoot(view, false).Run(); err != nil { panic(err) } } ```
kerem closed this issue 2026-03-04 01:02:27 +03:00
Author
Owner

@rivo commented on GitHub (Dec 3, 2018):

Hi Andreas,

Thanks for contributing. Is there any reason you want this specific example in the package? I'm trying to keep the demos as minimal as possible, to give people something simple to start with.

However, I'm happy to include it in the Wiki, if you want. Before doing that, I'd make the following modifications, though:

  • view.SetText() needs to be wrapped in an app.QueueUpdateDraw() call to avoid race conditions. See the Wiki for details.
  • refreshInterval should probably be a constant.
  • Combine var statements with brackets () (just a cosmetic change).

Let me know what you think about that.

<!-- gh-comment-id:443646575 --> @rivo commented on GitHub (Dec 3, 2018): Hi Andreas, Thanks for contributing. Is there any reason you want this specific example in the package? I'm trying to keep the demos as minimal as possible, to give people something simple to start with. However, I'm happy to include it in the [Wiki](https://github.com/rivo/tview/wiki/Modal), if you want. Before doing that, I'd make the following modifications, though: - `view.SetText()` needs to be wrapped in an `app.QueueUpdateDraw()` call to avoid race conditions. See the [Wiki](https://github.com/rivo/tview/wiki/Concurrency) for details. - `refreshInterval` should probably be a constant. - Combine `var` statements with brackets `()` (just a cosmetic change). Let me know what you think about that.
Author
Owner

@lnxbil commented on GitHub (Dec 10, 2018):

Sorry, for answering so late.
Yes, an example in the Wiki would be great, too. I didn't realize there is something in the wiki at all. Often I just try the examples in the GIT and do not explicitly look for other examples in the wiki - at least I normally do not expect to find other/additional examples. My bad. Now I also know where the postgres examples comes from and can play around with it.

Here is the updated code that can go in the wiki:

// Demo code for a timer based update
package main

import (
	"fmt"
	"time"

	"github.com/rivo/tview"
)

const refreshInterval = 500 * time.Millisecond

var (
	view *tview.Modal
	app  *tview.Application
)

func currentTimeString() string {
	t := time.Now()
	return fmt.Sprintf(t.Format("Current time is 15:04:05"))
}

func updateTime() {
	time.Sleep(refreshInterval)
	for {
		app.QueueUpdate(func() {
			view.SetText(currentTimeString())
			app.Draw()
		})
		time.Sleep(refreshInterval)
	}
}

func main() {
	app = tview.NewApplication()
	view = tview.NewModal().
		SetText(currentTimeString()).
		AddButtons([]string{"Quit", "Cancel"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			if buttonLabel == "Quit" {
				app.Stop()
			}
		})

	go updateTime()
	if err := app.SetRoot(view, false).Run(); err != nil {
		panic(err)
	}
}
<!-- gh-comment-id:445870090 --> @lnxbil commented on GitHub (Dec 10, 2018): Sorry, for answering so late. Yes, an example in the Wiki would be great, too. I didn't realize there is something in the wiki at all. Often I just try the examples in the GIT and do not explicitly look for other examples in the wiki - at least I normally do not expect to find other/additional examples. My bad. Now I also know where the postgres examples comes from and can play around with it. Here is the updated code that can go in the wiki: ```go // Demo code for a timer based update package main import ( "fmt" "time" "github.com/rivo/tview" ) const refreshInterval = 500 * time.Millisecond var ( view *tview.Modal app *tview.Application ) func currentTimeString() string { t := time.Now() return fmt.Sprintf(t.Format("Current time is 15:04:05")) } func updateTime() { time.Sleep(refreshInterval) for { app.QueueUpdate(func() { view.SetText(currentTimeString()) app.Draw() }) time.Sleep(refreshInterval) } } func main() { app = tview.NewApplication() view = tview.NewModal(). SetText(currentTimeString()). AddButtons([]string{"Quit", "Cancel"}). SetDoneFunc(func(buttonIndex int, buttonLabel string) { if buttonLabel == "Quit" { app.Stop() } }) go updateTime() if err := app.SetRoot(view, false).Run(); err != nil { panic(err) } } ```
Author
Owner

@rivo commented on GitHub (Dec 14, 2018):

I added your code to the Wiki with some minor changes: https://github.com/rivo/tview/wiki/Timer

The package itself only contains very simple examples to get people started. For more elaborate examples, we have the Wiki. The Wiki also contains longer discussions of various topics. For example, the page about Concurrency where I've also added a link to your Timer example.

<!-- gh-comment-id:447376583 --> @rivo commented on GitHub (Dec 14, 2018): I added your code to the Wiki with some minor changes: https://github.com/rivo/tview/wiki/Timer The package itself only contains very simple examples to get people started. For more elaborate examples, we have the Wiki. The Wiki also contains longer discussions of various topics. For example, the page about [Concurrency](https://github.com/rivo/tview/wiki/Concurrency) where I've also added a link to your Timer example.
Author
Owner

@ardnew commented on GitHub (Dec 14, 2018):

I use a similar pattern as this, but with Go's standard time.Ticker channel combined with tview.Box's provided SetDrawFunc() callback, which allows writing to arbitrary locations on screen. It's more primitive, but also a little more versatile and idiomatic in my opinion:

// Demo code for a timer based update
package main

import (
	"time"

	"github.com/gdamore/tcell"
	"github.com/rivo/tview"
)

const refreshInterval = 500 * time.Millisecond

var (
	view *tview.Box
	app  *tview.Application
)

func drawTime(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) {
	timeStr := time.Now().Format("Current time is 15:04:05")
	tview.Print(screen, timeStr, x, height/2, width, tview.AlignCenter, tcell.ColorLime)
	return 0, 0, 0, 0
}

func refresh() {
	tick := time.NewTicker(refreshInterval)
	for {
		select {
		case <-tick.C:
			app.QueueUpdateDraw(func() {})
		}
	}
}

func main() {
	app = tview.NewApplication()
	view = tview.NewBox().SetDrawFunc(drawTime)

	go refresh()
	if err := app.SetRoot(view, true).Run(); err != nil {
		panic(err)
	}
}
<!-- gh-comment-id:447433823 --> @ardnew commented on GitHub (Dec 14, 2018): I use a similar pattern as this, but with Go's standard `time.Ticker` channel combined with `tview.Box`'s provided `SetDrawFunc()` callback, which allows writing to arbitrary locations on screen. It's more primitive, but also a little more versatile and idiomatic in my opinion: ```go // Demo code for a timer based update package main import ( "time" "github.com/gdamore/tcell" "github.com/rivo/tview" ) const refreshInterval = 500 * time.Millisecond var ( view *tview.Box app *tview.Application ) func drawTime(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) { timeStr := time.Now().Format("Current time is 15:04:05") tview.Print(screen, timeStr, x, height/2, width, tview.AlignCenter, tcell.ColorLime) return 0, 0, 0, 0 } func refresh() { tick := time.NewTicker(refreshInterval) for { select { case <-tick.C: app.QueueUpdateDraw(func() {}) } } } func main() { app = tview.NewApplication() view = tview.NewBox().SetDrawFunc(drawTime) go refresh() if err := app.SetRoot(view, true).Run(); err != nil { panic(err) } } ```
Author
Owner

@ardnew commented on GitHub (Dec 14, 2018):

Also, note the empty anonymous function passed into QueueUpdateDraw() in my example. This is simply to enqueue a Draw() onto the event loop (without using a silly QueueUpdate(func(){ app.Draw() })) . If you pass a nil function in, it will segfault with a nil pointer dereference. I haven't looked at the tview code handling this, but I wonder if there is any way to safely have tview just enqueue the Draw() in this case and not attempt to exec the callback?

<!-- gh-comment-id:447436435 --> @ardnew commented on GitHub (Dec 14, 2018): Also, note the empty anonymous function passed into `QueueUpdateDraw()` in my example. This is simply to enqueue a `Draw()` onto the event loop (without using a silly `QueueUpdate(func(){ app.Draw() })`) . If you pass a nil function in, it will segfault with a nil pointer dereference. I haven't looked at the tview code handling this, but I wonder if there is any way to safely have tview just enqueue the `Draw()` in this case and not attempt to exec the callback?
Author
Owner

@rivo commented on GitHub (Dec 14, 2018):

Actually, if you call Application.Draw(), it is automatically queued. No need to use QueueUpdate() or QueueUpdateDraw() here. I clarified this in the package description:

You can also call Application.Draw() from any goroutine without having to wrap it in QueueUpdate().

Do you want me to add this to the Wiki, too? I wouldn't replace the other example because both programs illustrate different things:

  • @lnxbil updates a primitive in a goroutine, then redraws it.
  • In your code, you trigger a call to app.Draw(), then update your primitive in response to it.

I could post both in the Wiki along with some comments highlighting the differences.

<!-- gh-comment-id:447492355 --> @rivo commented on GitHub (Dec 14, 2018): Actually, if you call `Application.Draw()`, it is automatically queued. No need to use `QueueUpdate()` or `QueueUpdateDraw()` here. I clarified this in the [package description](https://godoc.org/github.com/rivo/tview#hdr-Concurrency): > You can also call Application.Draw() from any goroutine without having to wrap it in QueueUpdate(). Do you want me to add this to the Wiki, too? I wouldn't replace the other example because both programs illustrate different things: - @lnxbil updates a primitive in a goroutine, then redraws it. - In your code, you trigger a call to `app.Draw()`, then update your primitive in response to it. I could post both in the Wiki along with some comments highlighting the differences.
Author
Owner

@ardnew commented on GitHub (Dec 14, 2018):

Yeah that would be great if you added it. And thanks for the feedback, honestly wasn't sure if I was misusing tview this way

<!-- gh-comment-id:447502062 --> @ardnew commented on GitHub (Dec 14, 2018): Yeah that would be great if you added it. And thanks for the feedback, honestly wasn't sure if I was misusing `tview` this way
Author
Owner

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

I added your code, too.

<!-- gh-comment-id:447558472 --> @rivo commented on GitHub (Dec 15, 2018): I added your code, too.
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#155
No description provided.