IUP for GO

3-Toed-Sloth
9 min readJun 12, 2022

--

Created Sunday 12 June 2022

Recently, Milan Nikolic, the maintainer of the Go bindings for Raylib, has released renewed bindings to the cross-platform IUP framework for Go. I’ve chosen to use it for a project and report on its pros and cons. Check out this post for my review of other GUI toolkits.

Installation

Installation is very simple, you just import the package as usual into your Go project. The Readme says that the first compilation might take several minutes, but on my Linux machine it was very fast. Subsequent compilations are not noticable slower than pure Go on my machine. As the docs state, you have to make sure the dependencies are met on your system. On my machine, this meant that I had to install the GTK3 developer files using apt-get install libgtk-3-dev.

How it looks

Here is how my current, very alpha-stage project looks like with IUP on Linux (scaled down):

This is not too bad, given that there will be more user interface elements later and the visual look is not yet a priority. It uses a script like this for launching the application with the CBlack theme:

#!/bin/sh
GTK_THEME=CBlack ./giuprj

With my default settings on my current Linux installation, it looks visually less appealing. The window size is a bit different but otherwise no changes were made:

The bottom line is that at least on Linux there are options for styling the application, as long as you’re willing to override the user’s GTK theme settings. Purists may hate you for doing that but I actually recommend it if the GUI is supposed to be an alternative to another, more modern GUI option or a web GUI. Notice, however, that restyling options on Windows might be limited, and on Linux IUP clearly shows that it uses GTK as backend. Some of GTK’s choices unfortunately range from annoying to ugly, and there is nothing you can do about them. For instance, notice the dashed lines in the list boxes? These are drawn by GTK to indicate that there is more content to the right that has been “cut off” by the control dimensions, and there is no way to remove these except for cropping the text in the list.

On Windows and Mac, the design of some user interface elements cannot be changed and the options might be more limited. There are new controls in IUP prefixed with “Flat” that allow more customization and are drawn by IUP itself, but these are buggy and don’t always work as expected yet. But overall IUP looks good and it is in many cases desirable to have native controls, which is what IUP draws by default on Windows and Mac (and Linux, if you consider GTK native). The controls can be set to auto-resize in almost any conceivable way, and that is a great plus in contrast to frameworks like Fyne which will constantly annoy you with their layout limitations. Not visible in the screenshots because I made the handles invisible are the dividers / splitters: There are four different splitters that allow you to dynamically resize content areas when you drag them with the mouse. This works quite well and illustrates the maturity of the framework. IUP has been around since the 90s and is still being developed and updated regularly.

Usage

IUP is used in a very straightforward way and in fact the ease of use is one of the reason for its continuing popularity as a lightweight GUI framework. However, that way is decidedly not the Go way. You could say that IUP is completely alien to the Go way of doing things. In IUP all user interface properties are get and set with functions GetAttribute and SetAttribute that take and return strings — that’s right, even if the attribute is a number or a color or an image, you will generally set a string. So you need to convert in Go appropriately, e.g. by using strconv.Atoi to convert from string to integer, and strconv.Itoa to convert from integer to string. Obviously, this is highly inefficient, and indeed in my tests the list box was much slower than the one I used in the immediate mode GUI Giu. You can set and get string handles for objects and sometimes, like in case of images, have to do that, but you can store handles directly in Golang. In fact, all IUP controls and objects are of type iup.Ihandle so forget about type safety. IUP has a TCL/Tk like loosely typed, string based API.

To give you an idea of how this looks like, here is part of my window initialization:

a.previewName = iup.MultiLine().
SetAttributes(“VISIBLELINES=1,EXPAND=YES,BORDER=NO,MARGIN=0x0,CGAP=0x0,WORDWRAP=YES,BGCOLOR=” + dlgbgcolor)
a.previewLink = iup.Link(“”, “”).SetAttributes(“EXPAND=YES”)
a.previewSize = iup.Label(“”)
a.previewTags = iup.MultiLine().SetAttributes(“VISIBLELINES=2,EXPAND=HORIZONTAL,BGCOLOR=” + dlgbgcolor)
a.previewInfo = iup.MultiLine().SetAttributes(“VISIBLELINES=4,EXPAND=YES,BGCOLOR=” + dlgbgcolor)

As you can see, it’s very simple and takes almost no time to learn. What’s great about this binding is that it also contains the complete documentation online in the standard Go format:

https://pkg.go.dev/github.com/gen2brain/iup-go/iup

Each control has a link to the original docs, and since the API is pretty much language agnostic and string-based, you directly use these docs to look up everything you need. There is no weird guessing involved like in e.g. some Qt or GTK4 bindings for Go that require a bit more work to translate back and forth between the original docs and how to do it in Go. It’s all just strings and handles in IUP anyway.

Limitations

Concurrency Is Very Bad for IUP

There is one huge caveat: concurrency. As you probably already know, most GUI do not allow concurrent drawing or other control/widget access. Common drawing backends such as SDL and GLFW also require sequential drawing. It makes sense, given that in drawing operations the order generally matters.

However, the problem in IUP is not just lack of support for concurrency, it is that the framework will crash to desktop in very bad and mysterious ways with any kind of concurrent access to controls. This includes reading and writing their internal state, or setting other attributes, and it does not matter whether you control access on the Go side with mutexes or other synchronization primitives. At least some concurrent access to GUI elements results in crashes to the desktop, and I have not been able to determine exactly which ones. It was initially suggested to use the package mainthread that is often used for that purpose but I figured out in my tests that this does not work and Milan removed this suggestion from the docs. IUP will crash badly and unexpectedly when you allow different goroutines to access its objects.

There are essentially two solutions to this problem, which are both cumbersome in Go and very much at odds with its philosophy. First, you can use an iup.Timer to periodically grab closures from a concurrent-safe queue and execute this code in the timer’s action function. Second, you can do the same but with installing a pulling loop in an iup.IdleFunc. The two approaches are roughly equivalent. The timer approach has the disadvantage of executing functions with a speed given by its interval, and the idle function approach has the disadvantage of wasting CPU cycles which are, according to the docs, already wasted by simply installing the function. I chose the timer approach and my dispatch looks like this:

type Scheduler struct {
mainContext context.Context // the master context, from which others are derived
wg sync.WaitGroup // for waiting until all goroutines have finished
cancel func() // for canceling the master context, stopping all derived goroutines
once map[string]func() // for each key, only one goroutines can run at a time
idleQueue *goconcurrentqueue.FIFO // for idle functions
timer iup.Ihandle // IUP timer for processing queue
mutex sync.Mutex // for synchronization
}
// NewScheduler returns a new scheduler with a cancelable context based on the given context.
func NewScheduler(ctx context.Context) *Scheduler {
c, done := context.WithCancel(ctx)
scheduler := &Scheduler{
mainContext: c,
cancel: done,
once: make(map[string]func()),
idleQueue: goconcurrentqueue.NewFIFO(),
timer: iup.Timer(),
}
idleFunc := func(ih iup.Ihandle) int {
select {
case <-scheduler.mainContext.Done():
scheduler.timer.SetAttribute("RUN", "NO")
default:
for scheduler.idleQueue.GetLen() > 0 {
fn, err := scheduler.idleQueue.Dequeue()
if err != nil {
return iup.DEFAULT
}
f, ok := fn.(func())
if ok {
f()
}
iup.LoopStep()
}
}
return iup.DEFAULT
}
scheduler.timer.SetCallback("ACTION_CB", iup.TimerActionFunc(idleFunc))
scheduler.timer.SetAttributes("TIME=50,RUN=YES")
return scheduler
}

If all access to IUP elements is dispatched through this queue by a function of this Scheduler, no crashes occur and the framework is rock solid and stable.

However, IUP has refresh problems in tight loops, and for some reason in the Go port the recommendation of using iup.LoopStep or iup.Flush in tight loops to yield time to IUPs processing does not work adequately. IUP has an internal draw queue that is blocked when Go runs a tight loop, even if that loop is running in a separate goroutine. I don’t know how this is possible, but it’s what my tests clearly confirm!

So far, I have not found a solution to this problem. Under certain conditions, the IUP control elements freeze and no amount of trying to update them manually (iup.Refresh, iup.LoopStep, iup.Flush, iup.Update) alleviates the problem. It’s an infuriating flashback to the 90s, which is when I had this problem last time.

Sorry Dave, No Rich Text

The Go IUP bindings do not have any control that supports different text styles, fonts, or colors within the same multiline text edit field. Forget about any kind of rich text, syntax coloring, HTML-like styling options. You get basic multiline edit fields and can copy and set the selection, etc., but no rich text.

There is a Scintilla extension for IUP that allows for most of rich text editing, although Scintilla is primarily focused at fast syntax coloring and text editing. The IUP bindings allow for pretty extensive styled editing and only fall short of arbitrary image embeddings. However, these bindings are not available in the Go IUP port and the maintainer has mentioned in a Github issue that he does not plan to add Scintilla support in the future. The dependencies complicate things, and so the choice is understandable, but lack of any kind of rich text support is a big drawback.

No Fast Canvas Drawing

By the same token, optional fast canvas drawing primitives have also been omitted to the Go bindings, as well as other custom drawing-based extensions like a fast spreadsheet-like control. You will get basic CPU-based image drawing primitives and can use whatever Go’s image.Image may offer, e.g. use a third party library for image drawing, but there is no GPU-accelerated drawing within IUP. Together with the concurrent update/refresh problems mentioned above, this means that IUP cannot be used for anything game-like or any other fast graphics update like attempting to draw movies frame by frame. I mean, you can try, but it seems unlikely that this will work.

Small Bugs and Anomalies

I’ve experienced a number of small bugs and visual anomalies during the conversion of my project. For instance, I had to introduce a fixed-width spacer in a iup.Split control because controls to the right did not respect their border properties and leaped out of the window in an ugly way. Another, more serious bug is that at least on my developer machine with Gtk3, the BGCOLOR property of iup.Split was not set correctly, an anomaly that can only be fixed with a custom theme. However, overall the framework is very stable and mature, and you can achieve most layout choices with a bit of experimenting. Especially the border and control gap properties with inheritance are very flexible and allow you to fine-tweak the appearance in ways that more Go-like GUI options don’t allow you to do.

Summary

Go’s new IUP bindings are pretty nice and overall a good choice for small and medium-sized projects with moderate GUI requirements. It is very mature and draws native controls, as opposed to ad hoc styles that come with various disadvantages like lack of screen reader support. It is certainly a choice that only makes sense for very traditional GUI desktop applications with not too high requirements. If you plan to embed movies or web views and things like that, you’ll have to use Qt or GTK4. But I can recommend it for simpler projects and it’s fun to use — except for the dreaded concurrency/refresh issues from the 90s.

[https://slothblog.org]

--

--