Patrice Ferlet
Patrice Ferlet
Créateur de ce blog.
Publié le juin 29, 2020 Temps de lecture: 12 min

Me suivre sur Mastodon

Une UI cross-plateform, en Go, embarquée, ça existe

thumbnail for this post

Ce qui manquait à Golang c’était une librairie graphique pour faire des interfaces, mais sans dépendance, et qui marche partout, même en app Mobile - et bien Fyne c’est ça !

J’ai attaqué Go depuis quasiment le début de sa sortie (Merci Nicolas d’avoir insisté pour que je jette un oeil), ce langage m’a convaincu en quelques heures. J’adore ce langage pour sa philosophie, son fonctionnement, son principe clair et qui ne force pas la POO à foison. En Go, tout est logique, on vous pousse à coder et non à chercher comment coder.

Mais depuis le début, j’ai senti un manque, un vrai manque. Vous l’avez capté, à cause du titre, c’est la possibilité de coder des interfaces graphiques.

Alors oui, bien entendu, on peut utiiser GTK ou Qt, oui.

Mais un des principes de Go (qui peut déplaire, je le conçois) c’est que vous vous abstrayez des dépandances. J’entends par là que la compilation est (par défaut) statique, et que vous évitez dans 99% des cas d’avoir à installer des librairies externes à Go. Or, par exemple avec GTK, vous devez avoir les librairies Gtk-Dev pour compiler votre application et le client doit avoir la bonne version de GTK pour faire tourner le binaire. On est donc loin du compilé statique natif.

Autre chose, en GTK, il est difficile d’exploiter la puissance des goroutines - on est obligé de travailler avec des états de la Glib qui permettent de mettre en parallèle des traitements. Bref, ça fonctionne très bien mais ça n’est pas “Golang ready”.

Et puis, pour ajouter une cerise sur le gâteau déjà bien empilé, j’aimerai faire une application qui fonctionne partout, y compris sur du mobile.

Bref, j’avais entendu parler de “nuklear” mais la complexité du code me laissait de marbre.

Et je suis tombé par hasard sur la pépite, le graal, enfin bref le truc qui répond à mon besoin et potentiellement le votre. Ce petit bijou s’appelle Fyne.

Miiiiiissssss Fyne1 !

The nany

Fyne est une librairie qui fait partie d’un projet plus grand: conquérir le monde

Enfin pas exactement, hein. En gros il y a deux projets:

Le premier point, on ne va pas en parler ici - par contre la librairie graphique, elle, c’est du lourd.

Ils sont partis de zéro, pour que la définition des objets graphiques puissent répondre aux différents drivers que l’on peut utiliser. Ces drivers permettent d’afficher les objets de la même manière que vous soyez sur Linux, Windows, Mac, iOS ou Android.

Vous avez bien lu, on parle de Desktop et de Mobile. Vous allez voir, il suffit de compiler l’application pour la bonne cible et sans trop s’arracher la perruque.

Votre application contiendra, si vous le souhaitez, un gestionnaire de préférences, encore une fois sans vous soucier de l’emplacement des données puisque cela est toujours différent selon l’OS que vous ciblez.

Et le petit truc qui donne encore plus de saveur à ce projet, c’est que les développeurs ont inclu ce qu’il faut pour créer votre application en TDD2 et ce sans avoir à utiliser un serveur graphique. Vous trouverez une bon didacticiel dans cette vidéo:

Je ne vais pas en parler dans cet article, par contre je vous conseille de l’utiliser dans vos développements, parce que lorsque votre application commence à prendre un peu de poids, tout devient plus simple à corriger. Et le TDD est à mon avis l’une des méthodologies les plus intéressantes dans notre métier - on code un test qui décrit ce qu’on veut faire, et on fait en sorte que le test passe en codant la fonctionnalité.

Bon bref, voyons comment ça fonctionne.

Premier test, à l’arrache

On va se créer un petit projet, dans /tmp/test-fyne et utiliser aussi le gestionnaire de dépendances intégré à Go (il était temps qu’il y en ait):

# on crée le répertoire de demo
mkdir -p /tmp/test-fyne
cd /tmp/test-fyne
# on initialise notre gestion de dépendances
go mod init demo
# on installe Fyne
go get fyne.io/fyne

Et on commence par développer notre première application graphique, simplement:

package main

import (
    "fyne.io/fyne"
	"fyne.io/fyne/app"
)

func main(){
    // l'objet d'application
    application := app.New()

    // on se crée une fenêtre
    win := application.NewWindow("Hello")
    // comme on a pas de contenu... je vous conseille de mettre ça
    win.Resize(fyne.NewSize(400, 300)) 

    // on lance l'app
    // cette fonction est un alias à win.Show() et application.Run()
    win.ShowAndRun()
}

Alors, ne me dites pas que c’est compliqué. Testez l’application en lançant go run main.go et vous allez voir:

Notre première application

Bon, mettons nous tout de suite d’accord: le design est léger, pas forcément joli, voir “old-school”. Mais l’idée n’est pas de remplacer GTK, Qt, ou tout autre librairie graphique adaptée à votre environnement. Là, on parle de faire une application rapidement, cross-platform, et facile à déployer “partout”. Donc, on va s’y faire (et rien ne dit que les thèmes ne soient pas, plus tard, encore plus jolis).

Maintenant, on va ajouter des trucs dans cette fenêtre, un label (un texte…) et un bouton qui a pour action de fermer l’application - notez que j’ai supprimé le Resize et ajouté l’utilisation d’une boite verticale (VBox). Aussi, il faut ajouter fyne.io/fyne/widget dans les imports pour avoir accès à ces gadgets graphiques (c’est à peu près la traducition du mot widget).

package main

import (
	"fyne.io/fyne/app"
	"fyne.io/fyne/widget"
)

func main() {
	application := app.New()

	win := application.NewWindow("Hello")

	label := widget.NewLabel("Et un bouton, un")
	button := widget.NewButton("Click me", func() {
		application.Quit()
	})

	// une boite verticale pour les gouverner
	// et la fenêtre les lier
	box := widget.NewVBox(label, button)

	win.SetContent(box)
	win.ShowAndRun()
}

Et là, simplement:

Un bouton, un label, dans une boîte

Il y a à peu près tout ce dont on a besoin. Le package “widget” contient des boutons, labels, entrées de texte, etc. Dans “canvas” vous trouverez des conteneurs d’image, de dessins - mais vous avez aussi un package pour des boites de dialogue, de notifications, de thèmes… Donc à peu près tout ce dont on a besoin pour faire une application graphique complète

Bref, il faut fouiller un peu dans la documentation3 mais c’est vraiment pas très complexe.

Bon, vous avez aussi certainement remarqué l’abscence d’icône dans votre barre des tâches. Alors l’idée c’est d’éviter à tout prix d’avoir un fichier externe. On va donc installer la commande “fyne” pour transformer une image en resource (en code):

# installons la commande fyne
go get fyne.io/fyne/cmd/fyne

# allez chercher une icone, et créons la ressource en Go
fyne bundle face-monkey.png > icon.go

Vous pouvez regarder dans le fichier icon.go qui contient une variable nommée resourceFaceMonkeyPng, elle correspond à votre icône.

Il faut ajouter une petite ligne de code pour gérer ça:

// après la création de l'application:
application.SetIcon(resourceFaceMonkeyPng)

Dans ma barre des tâches, c’est bon j’ai l’icône:

L'icône dans la barre de taches, c'est OK

Et donc, le binaire contient l’icône, inutile de gérer des chemins etc… Effectivement c’est un peu réberbatif de devoir convertir vos icônes, mais en soit c’est une source d’énervement en moins pour livrer votre programme à quelqu’un.

Alors oui, coupons court à toutes discussions, le binaire de sortie est très lourd !

Le binaire va contenir les ressources et la librairie graphique, on table donc à minima sur du 17Mo pour une fenêtre. Mais après, clairement, quand l’interface sera plus fournie avec des évènements à gérer etc., le binaire ne va pas tant grossir. Et puis il faut faire un choix entre se battre avec des dépendances (et y arriver, je ne dis pas le contraire hein) ou faire un soft qui n’impose finalement qu’une contrainte de poids. À vous de voir.

Une petite subtilité: le redimensionnement

Rien n’est jamais parfait, et je me suis très vitre confronté à un petit souci qui m’a pris un peu de temps à résoudre. Heureusement, les auteurs sont à l’écoute et m’ont répondu sur GitHub.

Étant donné le travail fourni pour que les applications passent aussi bien sur Mac que Linux ou Windows, et sur les mobiles, la gestion du placement et des espaces d’éléments graphiques est parfois un peu complexe à comprendre. Prenons un exemple simple. Je veux afficher:

  • un label
  • un texte plus gros scrollable (par exemple une information de licence)
  • un bouton pour passer à la suite

Le tout dans une boite verticale. Naturellement je vais faire ceci

label := widget.NewLabel("Information de license")

// on fait un gros texte qu'on place dans un conteneur
// qui permet le défilement
license := widget.NewLabel(licenseText)
scrollable := widget.NewVScrollContainer(license)

button := widget.NewButton("Click me", func() {
	application.Quit()
})

// on place tout à la verticale
box := widget.NewVBox(label, scrollable, button)

Sauf que voilà… tout l’espace de la fenêtre n’est pas utilisé.

Tentez le coup, vous allez voir. La fenêtre se cale à la taille “définie” et si vous redimensionnez la fenêtre, tous les éléments restent en haut de la fenêtre. Or, on veut que notre texte s’étire sur la hauteur.

Quand on vient du monde GTK ou Qt, on se dit qu’il suffit de donner une indication à tel ou tel widget pour qu’il s’étire. Mais ce n’est pas le cas avec Fyne. Il va falloir connaitre un peu ce que sont les Layouts et jouer avec.

Avec un peu de lecture de doc, on comprend très vite comment faire. La solution ici est d’utiliser un Layout nommé BorderLayout qui se trouve dans le package à inclure fyne.io/fyne/layout. Et ce Layout doit servir à dimensionner un “conteneur” avec fyne.NewContainerWithLayout() (package fyne.io/fyne).

En soit, les VBox et HBox, par exemple, sont des Layout prédéfinis.

On peut d’ailleurs coder notre propres Layout.

Ce que fait ce BorderLayout, c’est de placer les éléments en “haut”, “bas”, “gauche” et “droite” de la fenêtre. Mais on peut omettre des éléments.

Ce qu’on va faire est:

  • poser notre label en “haut” (top)
  • poser le bouton en “bas” (bottom)
  • rien à doite et à gauche
  • et placer notre “scrolable” entre le haut et le bas

Si vous avez plusieurs éléments à mettre en haut et bas, alors vous devez les faire entrer dans des box (Vbox, Hbox ou autres conteneurs) et ce seront ces “box” qui devront être placées en “top” et “bottom”.

label := widget.NewLabel("Information de license")
license := widget.NewLabel(licenseText)
scrollable := widget.NewVScrollContainer(license)
button := widget.NewButton("Click me", func() {
	application.Quit()
})

// on definit un "conteneur" et on lui donne les éléments qui servent à calculer
// l'espaement et le placement - ici, on veut le label en haut et le bouton en bas
// mais rien à droite et à gauche
container := layout.NewBorderLayout(label, button, nil, nil)
// et notre boite utilisera ce conteneur, on a plus qu'à lui donner la
// liste des éléments (l'ordre n'importe pas ici)
box := fyne.NewContainerWithLayout(container, label, scrollable, button)

win.SetContent(box)

Fyne va alors utiliser le layout pour placer les éléments que vous lui avez donné, et au bon endroit, il place le scrollable là où il peut (au milieu) et le redimensionnement de la fenêtre va alors étirer l’élément.

La fenêtre peut être redimensionnée

Sans cela, vous devriez donner une taille fixe ou vous contenter d’une application qui ne redimensionne pas le contenu. C’est donc un point à savoir, et il faut vraiment prendre en compte ce comportement.

Compiler / packager votre application (desktop, mobile)

Vous pouver proposer le binaire tel quel, mais votre “client” va devoir lancer la commande dans un terminal. Et un terminal, ça fait peur.

En général, sous Linux, on doit proposer un fichier “.desktop” qui décrit l’application, son icône de lancement, son emplaement… Sous Mac il faut packager un “.dmg” et sous windows il faut embarquer l’image dans le binaire en “.exe”…

Et quand il faut fournir une application mobile, par exemple pour Android, là c’est un package .apk qu’il faut construire.

Du boulot donc et pas des moindres.

Du coup, une nouvelle fois, les auteurs de Fyne ont fait un excellent boulot - la commande fyne propose une sous-commande nommée “package”. On sent que les auteurs de la librairie ont eut les mêmes problèmes que la plupart d’entre nous pour faire des applications.

Les commandes sont les suivantes:

fyne package -os linux -icon myapp.png
fyne package -os darwin -icon myapp.png
fyne package -os windows -icon myapp.png
  • Pour Linux, un tarball est généré, contenant le fichier “.desktop” et une structure de répertoire à installer sur la machine cible.
  • Pour Mac (darwin), c’est un dmg tout ce qu’il y a de plus commun, qui est généré.
  • Et pour Windows, un executable avec l’icône embarquée.

Encore une fois, Fyne soulage vraiment les procédures en automatisant les tâches qui fâchent.

Et pour finir, vous pouvez générer les applications mobiles:

fyne package -os android -appID com.example.ma-demo -icon mobileIcon.png
fyne package -os ios - appID com.example.ma-demo -icon mobileIcon.png

Il vous faudra bien entendu choisir un appID unique, et avoir le nécessaire pour compiler vers la plateforme cible (adb pour Android, Xcode pour iOS) - mais encore une fois, c’est clair, net et ça soulage les nerfs.

Allez voir la page de documentation qui explique toute la procédure. Vous aurez toutes les informations nécessaires.

J’ai testé sur Android, ça fonctionne vraiment bien.

Le mot de la fin

Je suis vraiment impressionné par le boulot qui a été fourni, que ce soit en tant que compatibilité cross-platform, mais aussi en terme de simplicité et d’outillage. Fyne me permet de créer des applications clientes rapidement et sans que j’ai à me soucier de savoir si les machines cibles ont ou non les librairies graphiques. Cette notion “d’embarquer” les ressources (graphique et librairies dans un binaire) est exactement ce dont j’avais besoin.

Fyne est en cours d’écriture pour une nouvelle mouture, encore plus poussée, qui va proposer du data-binding et des animations. J’ai hâte de voir ça 🤯 - pour l’heure, je vous laisse jeter un oeil sur cette petite perle.

N’hésitez pas à partager vos remarques, vos tests et un grand merci à vous pour la lecture 😘


  1. Avouez que vous connaissez la référence 😁 Une nounou d’enfer ↩︎

  2. Test Driven Development, ou développement dirigé par des tests ↩︎

  3. C’est le lot de notre métier, il faut lire la documentation ↩︎

comments powered by Disqus