Native GTK+ apps written in Go
19 September 2017
James Ralphs
19 September 2017
James Ralphs
github.com/avelino/awesome-go#gui
In short, we are still missing a fully-featured, pure Go, GUI library.
However there are some creative solutions.
This talk will be about GTK+ 3 gotk3 bindings, and GTK+ 2 with the go-gtk bindings.
-- gotk3 examples
-- go-gtk demo page
-- PyGTK documentation.
A couple of sample projects:
-- github.com/jamesrr39/taskrunner-app (go-gtk)
-- github.com/jamesrr39/gtk3-image-gallery-demo-app (gotk3)
For Ubuntu:16.04:
Install C dependencies:
sudo apt update && sudo apt install -y libgtk-3-dev libcairo2-dev libglib2.0-dev go get github.com/gotk3/gotk3/gtk go install -tags gtk_3_18 github.com/gotk3/gotk3/gtk
Build for Ubuntu 16.04:
go build -tags gtk_3_18 -gcflags "-N -l" -o <output file> <entry file>
Install C dependencies:
sudo apt update && sudo apt install -y build-essential libgtk2.0-dev
Error handling omitted so it would fit on one slide. Taken from the gotk3 README.
package main import ( "github.com/gotk3/gotk3/gtk" ) func main() { gtk.Init(nil) win, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) win.SetTitle("Simple Example") win.Connect("destroy", func() { gtk.MainQuit() }) label, _ := gtk.LabelNew("Hello, gotk3!") win.Add(label) win.SetDefaultSize(800, 600) win.ShowAll() gtk.Main() }
padding := 5 hbox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, padding) label, _ := gtk.LabelNew("my label") hbox.PackStart(label, false, false, 0)
scrollWin, _ := gtk.ScrolledWindowNew(nil, nil) scrollWin.SetPolicy(gtk.POLICY_ALWAYS, gtk.POLICY_ALWAYS) scrollWin.Add(hbox)
While both sets of bindings have image from file path methods, in Go we would like support for showing an image.Image
.
pixbuf, _ := gdk.PixbufNew(gdk.COLORSPACE_RGB, true, 8, width, height) pixelSlice := pixbuf.GetPixels() // underlying slice of bytes. Each pixel is 4 bytes (r, g, b, a). log.Printf("red value of pixel (1, 0): %d\n", pixelSlice[4]) imageWidget, err := gtk.ImageNewFromPixbuf(pixbuf)
If you make changes to any widgets, remember to call myWidget.ShowAll()
You can find utilities to convert image.Image to gdk pixel buffers here: github.com/jamesrr39/go-gtk-extra
myWidget.Connect("clicked", func() { ... }) // also works for tab + space myWidget.Connect("size-allocate", func() { ... }) // on the widget size allocation changing window.Connect("destroy", func() { ... })
Card interface
type Card interface { Render() gtk.IWidget }
Can be coupled with
func (w *AppWindow) RenderCard(card Card) { if nil != w.contentContainer { w.contentContainer.Destroy() } w.contentContainer, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) w.contentContainer.PackStart(card.Render(), true, true, 0) w.outerContainer.PackStart(w.contentContainer, true, true, 0) w.win.ShowAll() }
where AppWindow
is a struct containing outerContainer, contentContainer, the window and data access objects.
GTK+ is not thread-safe, but has APIs for handling UI updates by different goroutines.
glib.IdleAdd(f func(), args ...interface{}) glib.IdleAdd(myLabel.SetText, "hello") // for those that like interface{} glib.IdleAdd(func() { loadingLabel.Destroy() imageWidgetContainer.PackStart(imageWidget, false, false, 0) imageWidgetContainer.ShowAll() }) // for everyone else
// in `func main()`, before gtk.Init(nil) glib.ThreadInit(nil) gdk.ThreadsInit() gdk.ThreadsEnter() // later on, from your goroutine: gdk.ThreadsEnter() myLabel.SetText(calculationResult) gdk.ThreadsLeave()
Card.Render()
method, or an onClose()
method to the Card
interface.
github.com/jteeuwen/go-bindata is a package that turns binary assets into Go source code.
- Basically turns binary files into: var _fileX = []byte("\xff\ ... )
- Has worked well for me for web resources.
- Simple and effective way of keeping the "one binary" deployment.
exec.Cmd("google-chrome", "--app=file:///path/to/resource.ext").Run()
(google-chrome binary name may vary by platform)
19 September 2017
James Ralphs