Explore Fyne, a GUI framework for Go
Programming Snapshot – Fyne
With the Fyne framework, Go offers an easy-to-use graphical interface for all popular platforms. As a sample application, Mike uses an algorithm to draw arrows onto images.
My new favorite language, Go, impresses with its image processing routines, manipulating the pixels in an image just as quickly as ordinary numbers or text [1]. I often paint arrows in digital images, either for illustration purposes or to point out something funny in a snapshot, and I wondered how hard it would be to automate this task. Thus far, I have always had to fire up Gimp, select a path with the Path tool, and then place an arrow in the image with an Arrow plugin that I downloaded from somewhere on the Internet. With Go, there's got to be an easier way!
GUI Preferred
Actually, I prefer command-line tools, but sometimes a traditional graphical interface is just more practical, for example, to select a place in a photo where I want the program to draw a red arrow.
The arrow in Figure 1 illustrates, for example, that the tape measure in the photo, which I found while rummaging through my dusty desk drawers, is an age-old tchotchke from the once thriving company Netscape. Remember them? Yes, the browser manufacturer, where I worked for a few years during the 1990s, shortly after I left Munich and escaped to Silicon Valley "for a year" but somehow never found my way back.
This tape measure is thus more than 20 years old and was my antique contribution to the recent 25th anniversary celebrations of the German edition of Linux Magazine. I fondly remember the day when a young lady from Netscape's own ergonomics department presented it to me so that I could adjust my desk chair to a height that was gentle on my back and hand tendons. Talk about traveling down memory lane!
Spoiled for Choice
For mouse-driven input on graphical interfaces, Go provides about half a dozen different GUI frameworks, described by Andrew Williams' excellent book [2] with rewarding examples, and all more or less run across platforms (i.e., on Linux, the Mac, and even Windows, Remember them?) All based on a single codebase, and one of the frameworks is even supposed to run on mobile phones at some point, a real marvel of technology!
Another alternative for the problem solved today would have been the Electron GUI, which runs with JavaScript and made its debut in this column at the end of last year [3].
Williams also published a Go-based GUI package named Fyne [4] that looked very appealing to me aesthetically, so I used it for the arrow software that had popped into my head. A simple "Hello World" program in Fyne is created with just a few lines of Go code, as illustrated by Listing 1 [5]. It sets a text label and a button one below the other and then waits for the user to click the Exit button with the mouse. As always in Go, Listing 1 compiles with a two-liner:
go mod init hello go build
Listing 1
hello.go
01 package main 02 03 import "fyne.io/fyne/app" 04 import "fyne.io/fyne/widget" 05 06 func main() { 07 app := app.New() 08 09 win := app.NewWindow("Hello World") 10 win.SetContent(widget.NewVBox( 11 widget.NewLabel("Hello World"), 12 widget.NewButton("Quit", func() { 13 app.Quit() 14 }), 15 )) 16 17 win.ShowAndRun() 18 }
The new Go module created with go mod init
hello
causes the compiler in the build statement to fetch all the packages not yet installed from the GitHub repositories specified in the code, making them available to the current and future builds requiring them.
The result is a binary file hello
that doesn't need anything else to run and looks almost exactly the same on every target platform after recompiling. However, in order for Go to find the correct libraries for Ubuntu during compilation, the following packages must be installed:
sudo apt-get install libgl1-mesa-dev xorg-dev
On the Mac, the whole thing works without further ado.
Hello World
Listing 1 first creates an app, and then its one and only window. The SetContent()
method adds a VBox
widget to the window in line 10, which in turn arranges two widgets stacked on top of each other: The first one is a label reading "Hello World," and the second is a button that invites users to exit the program with Quit
.
The click action assigned to the button is accepted by the NewButton()
constructor in line 12 as a callback function. The callback in this case just invokes app.Quit()
and removes the GUI, leaving the user with a clean shell prompt.
Now that all widgets and their potential interactions are defined, it is up to line 17 to jump into the event loop with ShowAndRun()
, which will be waiting for user input while refreshing the UI continuously.
Now, let's move on to something more challenging – how to pop up a GUI that draws an arrow on a displayed photo at a location picked by the user. Listing 2 shows the UI application that opens a window and uses NewImageFromFile()
to prepare a JPG file specified by the user on the command line for loading it later on.
Listing 2
picker.go
01 package main 02 03 import ( 04 "flag" 05 "fmt" 06 "fyne.io/fyne" 07 "fyne.io/fyne/app" 08 "fyne.io/fyne/canvas" 09 "fyne.io/fyne/widget" 10 "image/jpeg" 11 "log" 12 "os" 13 ) 14 15 type clickImage struct { 16 widget.Box 17 image *canvas.Image 18 filename string 19 } 20 21 func (ci *clickImage) Tapped( 22 pe *fyne.PointEvent) { 23 imgAddArrow(ci.filename, 24 pe.Position.X, pe.Position.Y) 25 canvas.Refresh(ci) 26 } 27 28 func (ci *clickImage) TappedSecondary( 29 *fyne.PointEvent) { 30 // empty, but required for Tapped to work 31 } 32 33 func main() { 34 flag.Parse() 35 flag.Usage = func() { 36 fmt.Printf("usage: %s imgfile\n", 37 os.Args[0]) 38 } 39 40 if len(flag.Args()) != 1 { 41 flag.Usage() 42 os.Exit(2) 43 } 44 45 file := flag.Arg(0) 46 47 os.Setenv("FYNE_SCALE", ".25") 48 win := app.New().NewWindow("Pick Arrow") 49 img := canvas.NewImageFromFile(file) 50 51 width, height := imgDim(file) 52 clickimg := clickImage{image: img, 53 filename: file} 54 clickimg.image.SetMinSize( 55 fyne.NewSize(width, height)) 56 clickimg.Append(clickimg.image) 57 win.SetContent(&clickimg) 58 win.Resize(fyne.NewSize(width, height)) 59 win.ShowAndRun() 60 } 61 62 func imgDim(file string) (int, int) { 63 src, err := os.Open(file) 64 if err != nil { 65 log.Fatalf("Can't read %s", file) 66 } 67 defer src.Close() 68 69 jimg, err := jpeg.Decode(src) 70 if err != nil { 71 log.Fatalf("Can't decode %s: %s", 72 file, err) 73 } 74 75 bounds := jimg.Bounds() 76 return (bounds.Max.X - bounds.Min.X), 77 (bounds.Max.Y - bounds.Min.Y) 78 }
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.
-
Fedora 41 Released with New Features
If you're a Fedora fan or just looking for a Linux distribution to help you migrate from Windows, Fedora 41 might be just the ticket.
-
AlmaLinux OS Kitten 10 Gives Power Users a Sneak Preview
If you're looking to kick the tires of AlmaLinux's upstream version, the developers have a purrfect solution.
-
Gnome 47.1 Released with a Few Fixes
The latest release of the Gnome desktop is all about fixing a few nagging issues and not about bringing new features into the mix.
-
System76 Unveils an Ampere-Powered Thelio Desktop
If you're looking for a new desktop system for developing autonomous driving and software-defined vehicle solutions. System76 has you covered.
-
VirtualBox 7.1.4 Includes Initial Support for Linux kernel 6.12
The latest version of VirtualBox has arrived and it not only adds initial support for kernel 6.12 but another feature that will make using the virtual machine tool much easier.