Here's a feature comparison table of the native cross-platform GUI libraries for Go (web technologies based ones are completely skipped):
Feature | Fyne | GoGi | Gio | Qt Bindings | andlabs/ui |
---|---|---|---|---|---|
Basic Widgets | |||||
Labels | ✅ | ✅ | ✅ | ✅ | ✅ |
Buttons | ✅ | ✅ | ✅ | ✅ | ✅ |
Text Input | ✅ | ✅ | ✅ | ✅ | ✅ |
Checkbox | ✅ | ✅ | ✅ | ✅ | ✅ |
Radio Buttons | ✅ | ✅ | ✅ | ✅ | ✅ |
Progress Bar | ✅ | ✅ | ✅ | ✅ | ✅ |
Advanced Widgets | |||||
Tables/Lists | ✅ | ✅ | ✅ | ✅ | ✅ |
Tree View | ✅ | ✅ | ✅ | ✅ | |
Tab Container | ✅ | ✅ | ✅ | ✅ | |
Menu Bar | ✅ | ✅ | ✅ | ✅ | |
Context Menu | ✅ | ✅ | ✅ | ||
Graphics | |||||
Custom Drawing | ✅ | ✅ | ✅ | ✅ | |
2D Graphics | ✅ | ✅ | ✅ | ✅ | ❌ |
3D Support | ✅ | ✅ | ❌ | ||
Layouts | |||||
Grid | ✅ | ✅ | ✅ | ✅ | |
Box/Flow | ✅ | ✅ | ✅ | ✅ | ✅ |
Custom Layouts | ✅ | ✅ | ✅ | ✅ | ❌ |
Platform Features | |||||
Native Dialogs | ✅ | ✅ | ✅ | ✅ | |
System Tray | ✅ | ❌ | ✅ | ❌ | |
Notifications | ✅ | ❌ | ❌ | ✅ | ❌ |
Other | |||||
Mobile Support | ✅ | ✅ | ✅ | ❌ | |
Animation | ✅ | ✅ | ✅ | ✅ | ❌ |
Theming | ✅ | ✅ | ✅ | ✅ | ❌ |
Accessibility | ✅ | ✅ | |||
Binary Size | Small | Medium | Small | Large | Small |
Native Look & Feel | ❌ | ❌ | ❌ | ✅ | ✅ |
Development Activity | High | Medium | High | Medium | Low |
Legend:
- ✅ Supported
⚠️ Limited/Basic support- ❌ Not supported or requires significant custom work
Here's a simplified comparison of cross-platform GUI libraries for Go, excluding those based on web technologies (Wails, Lorca, WebView). Each of these libraries takes a different approach to creating GUIs without relying on web technologies, offering varying levels of control, appearance, and complexity.
- Approach: Native Go implementation using OpenGL
- Pros: Lightweight, easy API, good documentation, actively maintained
- Cons: Custom-styled widgets (not native OS look)
- Best for: Simple applications where file size matters
- Approach: Pure Go GUI toolkit
- Pros: Rich widget set, 3D capabilities, built-in GoLang IDE
- Cons: Less mature documentation, steeper learning curve
- Best for: Data visualization and complex applications
- Approach: Immediate mode GUI in Go
- Pros: Good performance, low-level control, mobile support
- Cons: Still maturing, requires more code for common UI elements
- Best for: Performance-critical applications, custom interfaces
- Approach: Bindings for C++ Qt framework (e.g., therecipe/qt)
- Pros: Mature, feature-rich, native look and feel
- Cons: Large dependencies, complex setup
- Best for: Enterprise applications requiring native appearance
- Approach: Lightweight wrapper around platform-native GUI libraries
- Pros: Native look and feel, small API surface
- Cons: Limited feature set, less actively maintained
- Best for: Simple utility applications requiring native appearance
Here's a minimal implementation of a tickable digital clock for each library. Each example showcases the basic patterns for rendering a dynamic UI element and handling updates with each library's specific approach to layout, events, and timers.
package main
import (
"time"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
window := myApp.NewWindow("Clock")
clock := widget.NewLabel("")
go func() {
for {
timeStr := time.Now().Format("15:04:05")
clock.SetText(timeStr)
time.Sleep(time.Second)
}
}()
window.SetContent(clock)
window.ShowAndRun()
}
package main
import (
"time"
"github.com/goki/gi/gi"
"github.com/goki/gi/gimain"
)
func main() {
gimain.Main(func() {
mainWindow := gi.NewMainWindow("Clock", "Digital Clock", 800, 600)
windowView := mainWindow.WindowView()
vp := windowView.AddNewVp(0, "clock-view")
clockLabel := gi.AddNewLabel(vp, "clock", "")
clockLabel.SetProp("text-align", gi.AlignCenter)
clockLabel.SetProp("font-size", "xx-large")
go func() {
for {
timeStr := time.Now().Format("15:04:05")
clockLabel.SetText(timeStr)
clockLabel.Update()
time.Sleep(time.Second)
}
}()
windowView.SetFullReRender()
mainWindow.StartEventLoop()
})
}
package main
import (
"image"
"log"
"time"
"gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/text"
"gioui.org/widget/material"
)
func main() {
go func() {
w := app.NewWindow()
th := material.NewTheme(gofont.Collection())
var ops op.Ops
currentTime := time.Now().Format("15:04:05")
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case e := <-w.Events():
switch e := e.(type) {
case app.FrameEvent:
gtx := layout.NewContext(&ops, e)
layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
label := material.H2(th, currentTime)
label.Alignment = text.Middle
return label.Layout(gtx)
})
e.Frame(gtx.Ops)
case app.DestroyEvent:
return
}
case <-ticker.C:
currentTime = time.Now().Format("15:04:05")
w.Invalidate()
}
}
}()
app.Main()
}
package main
import (
"time"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/widgets"
)
func main() {
app := widgets.NewQApplication(0, nil)
window := widgets.NewQMainWindow(nil, 0)
window.SetWindowTitle("Digital Clock")
window.SetMinimumSize2(250, 150)
centralWidget := widgets.NewQWidget(nil, 0)
window.SetCentralWidget(centralWidget)
layout := widgets.NewQVBoxLayout()
centralWidget.SetLayout(layout)
clockLabel := widgets.NewQLabel2("", nil, 0)
clockLabel.SetAlignment(core.Qt__AlignCenter)
font := clockLabel.Font()
font.SetPointSize(24)
clockLabel.SetFont(font)
layout.AddWidget(clockLabel, 0, 0)
timer := core.NewQTimer(nil)
timer.ConnectTimeout(func() {
timeStr := time.Now().Format("15:04:05")
clockLabel.SetText(timeStr)
})
timer.Start(1000) // 1000ms = 1s
window.Show()
app.Exec()
}
package main
import (
"time"
"github.com/andlabs/ui"
)
func main() {
err := ui.Main(func() {
window := ui.NewWindow("Digital Clock", 200, 100, false)
window.SetMargined(true)
clockLabel := ui.NewLabel("")
box := ui.NewVerticalBox()
box.SetPadded(true)
box.Append(clockLabel, false)
window.SetChild(box)
ticker := time.NewTicker(time.Second)
go func() {
for range ticker.C {
ui.QueueMain(func() {
clockLabel.SetText(time.Now().Format("15:04:05"))
})
}
}()
window.OnClosing(func(*ui.Window) bool {
ui.Quit()
return true
})
window.Show()
})
if err != nil {
panic(err)
}
}