Discarding photo fails with Go and Fyne

Faster Thanks to Prepping

Newly loaded photos are displayed by showImage() from line 37 onwards; it tries to load them from the cache first and, if this fails, scrapes them from the disk using loadImage() and decodes them. This takes time, but at some point this has to be done. Because of this delay of a good second, preloadImage() uses a Goroutine starting in line 31 with go func to handle loading in the background while the main program continues to run and respond to user input. When the user requests the next image, it is usually already in the cache, and showImage() fetches it and sends it to the screen at lightning speed.

Invisible Cache

Thanks to the hashicorp/golang-lru package from GitHub, the LRU cache maintained in the Cache global variable need not worry about wasted RAM. Line 23 in Listing 2 defines a cache with a maximum of 128 entries for preprocessed images, into which Add() (line 45, Listing 3) inserts new items under the photo's file path URL, while Get() (line 39) retrieves them from the cache. Although the LRU cache provides the Contains() function that determines whether an item is in the cache, users should always try to retrieve an item if they actually need it in order to avoid race conditions. Otherwise, although Contains() might report that the entry is there, a concurrent program could also cause it to disappear before the following Get().

If the cache is full, Add() simply throws the oldest entry out of the cache before inserting the new one, according to LRU rules. For the app, which later may still be looking for the old entry because the user went back to that old photo, this is not the end of the world. It can simply retrieve the image from the disk and put it back in the cache. This takes a bit longer, but in this inconvenient case you just have to wait.

By the way, one more subtle feature of Go's strict type system: Containers such as the LRU cache let you store generic data types. Therefore, when fetching, you must make sure that the entry is given the correct type again using runtime type assertions. For example, line 42 converts a found cache entry into a pointer to a canvas.Image type because the retrieved photo is of that type, even though the LRU cache had stored it as a generic interface{} type in the meantime.

Such manipulations wreak havoc with Go's strict type system, however. What Go normally fields at compile time therefore turns into an annoying runtime error if these conversions have not been carefully tested.

By the way, Fyne normally avoids absolute coordinate values in layout instructions and scales widgets like buttons automatically. Because of the widgets' captions and the font used, this can be done without explicit layout instructions. However, this is not possible with photos because Fyne has no way of knowing how big the containing canvas object needs to actually appear on the screen. If you don't give Fyne a minimum size for the widget in the form of SetMinSize(), you may have to use a magnifying glass to search the screen for the widget you are looking for. Without defaults, Fyne paints 0x0-sized images, often resulting in perplexingly hard-to-find app windows. If you specify the minimum size, like in line 66 of Listing 3, you can see what's going on.

Finally, Listing 4 implements the virtual trash can that uses toTrash() to dump photo files to a newly created old/ directory as needed. This is done by the Rename() function from the standard os package, which works without complaint as long as the original and the target file reside on the same storage medium.

Listing 4

trash.go

package main
import (
  "os"
  "path/filepath"
  "fyne.io/fyne/v2"
)
const TrashDir = "old"
func toTrash(file fyne.URI) {
  err := os.MkdirAll(TrashDir, 0755)
  panicOnErr(err)
  err = os.Rename(file.Name(),
    filepath.Join(TrashDir, file.Name()))
  panicOnErr(err)
}
func panicOnErr(err error) {
  if err != nil {
    panic(err)
  }
}

Adapting the Look & Feel

To make Go download the Fyne packages used in the listings from the server at fyne.io onto your local system and compile the whole enchilada, you need the sequence in Listing 5.

Listing 5

trash.go

$ go mod init inuke
$ go mod tidy

A subsequent

go build inuke.go image.go trash.go

should then generate a binary inuke without any errors. Upon launch, it will display the first photo in the current directory inside its GUI window. In the usual Go style, the compiler can also cross-compile for other platforms. In the case of Fyne, this even goes so far as to have the GUI code draw in the look and feel of the other platform. How this works is described in detail in the Fyne book by the Fyne guru himself [4]. The code from the three listings also compiles without problems on a Mac and results in a slightly adapted Apple look, as shown in Figure 4.

Figure 4: An app compiled for the Mac from the same code.

The graphical interfaces' implementation also depends heavily on the operating system you use. On Linux, Fyne taps into the libx11-dev, libgl1-mesa-dev, lbxcursor-dev, and xorg-dev libraries using a C wrapper from Go. You need to install these on Ubuntu, for example, by typing

sudo apt-get install

to ensure that a subsequent go build for a Fyne app actually finds the required underpinnings.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • GUI Apps with Fyne

    The Fyne toolkit offers a simple way to build native apps that work across multiple platforms. We show you how to build a to-do list app to demonstrate Fyne's power.

  • Treasure Hunt

    A geolocation guessing game based on the popular Wordle evaluates a player's guesses based on the distance from and direction to the target location. Mike Schilli turns this concept into a desktop game in Go using the photos from his private collection.

  • Straight to the Point

    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.

  • Chip Shot

    We all know that the Fyne framework for Go can be used to create GUIs for the desktop, but you can also write games with it. Mike Schilli takes on a classic from the soccer field.

  • Digital Shoe Box

    In honor of the 25th anniversary of his Programming Snapshot column, Mike Schilli revisits an old problem and solves it with Go instead of Perl.

comments powered by Disqus
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.

Learn More

News