I decided to write one cross-platform desktop application on Go . Made a CLI version, everything works fine. Yes, and Go Cross-compilation is supported. Everything is fine in general. But I also needed a GUI version. And then it began ...
The application had to be cross-platform.
Therefore, it should compile under Windows , GNU / Linux and macOS .
The choice fell on such libraries:
Electron and other frameworks that pull Chromium and node.js with them, I threw away as they weigh a lot, they also eat up a lot of operating system resources.
Now a little about each library.
Binding of the library GTK + 3 . Coverage is not all possibilities, but all the main presence.
The application is compiled using the standard go build
. Cross-platform compilation is possible, with the exception of macOS . Only with macOS you can compile for this OS, and with macOS it will be possible to compile under Windows + GNU / Linux .
The interface will appear natively for GNU / Linux , Windows (you will need to specify a special theme). For macOS it will not look native. It is possible to get out only if it is a terrible topic that will emulate the native elements of macOS .
Binding the Qt 5 library. Support for QML standard widgets. In general, many people advise this binding.
It is compiled using the qtdeploy
special command. In addition to the desktop platforms, there are also mobile ones. Crosscompilation takes place using Docker . For Apple operating systems , you can only compile with macOS .
If desired, Qt can be achieved so that the interface looks natively on desktop operating systems.
The library, which was originally written in C , the author has screwed it to many languages, including Go . Native webview is used to display: Windows - MSHTML , GNU / Linux - gtk-webkit2 , macOS - Cocoa / WebKit . In addition to the Go code, you will need to pee on JS as well, and HTML will come in handy.
Compiled with go build
, cross- compiling possible with xgo .
Looks natively can as far as the standard browser allows.
Why did I choose gotk3 ?
In therecipe / qt, I didn’t like the application’s very complicated build system, they even made a special command.
zserge / webview does not seem to be bad, it will not weigh much, but still it is a webview and there may be standard problems that occur in such applications: something may go somewhere. And this is not Electron , where the advanced Chromium is always bundled, and everything can go to some old Windows . And besides, you also have to write on JS .
gotk3 I chose as something in between. You can build a standard go build
, it looks acceptable, and indeed I love GTK + 3 !
In general, I thought everything would be simple. And that for nothing about Go say that in it a problem with GUI . But how wrong I was ...
Install everything from gotk3 ( gtk , gdk , glib , cairo ) to yourself:
go get github.com/gotk3/gotk3/...
Also, your system should have the GTK + 3 library itself in development.
In Ubuntu :
sudo apt-get install libgtk-3-dev
In Arch Linux :
sudo pacman -S gtk3
Via Homebrew :
brew install gtk-mac-integration gtk+3
Everything is not so simple here. The official instructions suggest using MSYS2 and already do everything in it. Personally, I wrote code on other operating systems, and did cross-compilation for Windows in Arch Linux , which I hope to write soon.
Now we write a small file with the main.go
code:
package main import ( "log" "github.com/gotk3/gotk3/gtk" ) func main() { // GTK. gtk.Init(nil) // , // "destroy" // win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) if err != nil { log.Fatal(" :", err) } win.SetTitle(" ") win.Connect("destroy", func() { gtk.MainQuit() }) // l, err := gtk.LabelNew(", gotk3!") if err != nil { log.Fatal(" :", err) } // win.Add(l) // win.SetDefaultSize(800, 600) // win.ShowAll() // GTK ( ). // gtk.MainQuit() gtk.Main() }
You can compile using the go build
, and then run the binary. But we just run it:
go run main.go
After launch, we get a window of this type:
Congratulations! You have a simple app from README gotk3 !
More examples can be found on Github gotk3 . I will not disassemble them. Let's better deal with what is not in the examples!
There is such a thing for Gtk + 3 - Glade . This is the GUI Designer for GTK + . It looks like this:
In order not to create each element manually and not to place it somewhere in the window with the help of the program code, you can distribute the whole design in Glade . Then save everything to an XML-like * .glade file and load it already through our application.
Installing the glade in GNU / Linux distributions is not difficult. In some Ubuntu it will be:
sudo apt-get install glade
In Arch Linux :
sudo pacman -S glade
In downloads from the official site is very old build. Therefore, it is better to install via Homebrew :
brew install glade
And then run:
glade
You can download the latest version here . I personally did not install it on Windows at all, so I don’t know about the stability of Glade work there.
In general, I designed a window like this:
Saved and received the main.glade
file:
<?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.22.1 --> <interface> <requires lib="gtk+" version="3.20"/> <object class="GtkWindow" id="window_main"> <property name="title" translatable="yes"> Glade</property> <property name="can_focus">False</property> <child> <placeholder/> </child> <child> <object class="GtkBox"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="margin_left">10</property> <property name="margin_right">10</property> <property name="margin_top">10</property> <property name="margin_bottom">10</property> <property name="orientation">vertical</property> <property name="spacing">10</property> <child> <object class="GtkEntry" id="entry_1"> <property name="visible">True</property> <property name="can_focus">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkButton" id="button_1"> <property name="label" translatable="yes">Go</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkLabel" id="label_1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">This is label</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> </object> </child> </object> </interface>
That is, we have a window_main
window ( GtkWindow
), in which inside a container ( GtkBox
), which contains an entry field entry_1
( GtkEntry
), a button button_1
( GtkButton
) and a label label_1
( GtkLabel
). In addition, there are still attributes of sampling (I set up a bit), visibility and other attributes that Glade added automatically.
Let's now try to download this presentation in our main.go
:
package main import ( "log" "github.com/gotk3/gotk3/gtk" ) func main() { // GTK. gtk.Init(nil) // b, err := gtk.BuilderNew() if err != nil { log.Fatal(":", err) } // Glade err = b.AddFromFile("main.glade") if err != nil { log.Fatal(":", err) } // ID obj, err := b.GetObject("window_main") if err != nil { log.Fatal(":", err) } // gtk.Window // "destroy" // win := obj.(*gtk.Window) win.Connect("destroy", func() { gtk.MainQuit() }) // win.ShowAll() // GTK ( ). // gtk.MainQuit() gtk.Main() }
Run again:
go run main.go
And we get:
Hooray! Now we are submitting the form with an XML- main.glade
file, and the code in main.go
!
The window starts up, but let's add interactivity. Let the text from the input field when you click on the button will fall into the label.
To do this, first we get the elements of the input field, the button and the label in the code:
// obj, _ = b.GetObject("entry_1") entry1 := obj.(*gtk.Entry) // obj, _ = b.GetObject("button_1") button1 := obj.(*gtk.Button) // obj, _ = b.GetObject("label_1") label1 := obj.(*gtk.Label)
I do not handle the errors that the GetObject()
function returns to make the code simpler. But in a real working application they must be processed.
Good. With the code above, we get our form elements. And now let's process the signal of the clicked
button (when the button is pressed). The GTK + signal is essentially a reaction to an event. Add the code:
// button1.Connect("clicked", func() { text, err := entry1.GetText() if err == nil { // label1.SetText(text) } })
Now run the code:
go run main.go
After entering some text in the field and clicking on the Go button, we will see this text in the label:
Now we have an interactive application!
At this stage, everything seems simple and does not cause difficulties. But I had difficulties with cross-compilation (after all, gotk3 compiles with CGO ), integration with operating systems and the file selection dialog. I even added a native dialogue to the gotk project. Also in my project needed internationalization. There are some features there too. If you are interested to see it all now in code, then you can peep here .
The source codes of the examples from the article are here .
And if you want to read the sequel, you can vote. And if it turns out to be someone interesting, I will continue to write.
Source: https://habr.com/ru/post/420035/
All Articles