// Fonctionnel

Créer une interface graphique GTK en Haskell

On associe souvent Haskell au calcul pur, aux types et à la ligne de commande. Pourtant, rien n’empêche d’écrire une interface graphique en Haskell. La bibliothèque GTK, qui motorise le bureau GNOME et d’innombrables applications Linux, dispose de bindings Haskell mûrs et confortables. Dans ce tutoriel, nous allons construire une petite fenêtre avec un bouton et brancher une action dessus, ce qui suffit à comprendre le modèle événementiel de GTK.

gtk2hs ou haskell-gi ?

Historiquement, les bindings GTK pour Haskell portaient le nom de gtk2hs. C’est le projet qui a longtemps servi de référence, exposant l’API de GTK 2 sous forme de modules Haskell. Il fonctionne toujours et beaucoup de tutoriels s’y réfèrent.

Le projet a ensuite évolué vers haskell-gi, qui génère automatiquement les bindings à partir des fichiers d’introspection GObject Introspection. C’est aujourd’hui l’approche recommandée pour cibler GTK 3 ou GTK 4 : le paquet s’appelle gi-gtk. Les deux partagent la même philosophie ; nous montrerons ici les deux styles pour que vous reconnaissiez le code que vous croiserez.

Installer les dépendances

GTK est une bibliothèque C : il faut donc que ses en-têtes de développement soient présentes sur le système avant de compiler le binding.

# Debian / Ubuntu
sudo apt install libgtk-3-dev

# Fedora
sudo dnf install gtk3-devel

Ensuite, on ajoute le paquet Haskell au projet. Avec Stack, on déclare la dépendance dans le fichier package.yaml :

dependencies:
  - base
  - gi-gtk
  - haskell-gi-base

Avec Cabal, on ajoute simplement gi-gtk à la section build-depends du fichier .cabal. La compilation télécharge et bâtit le binding ; la première fois peut être longue, c’est normal.

Une première fenêtre

Voici le squelette minimal d’une application gi-gtk. On initialise GTK, on crée une fenêtre, on demande son affichage et on lance la boucle d’événements.

{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified GI.Gtk as Gtk

main :: IO ()
main = do
  _ <- Gtk.init Nothing

  fenetre <- Gtk.windowNew Gtk.WindowTypeToplevel
  Gtk.setWindowTitle fenetre "Bonjour Coder Studio"
  Gtk.setWindowDefaultWidth  fenetre 320
  Gtk.setWindowDefaultHeight fenetre 200

  Gtk.widgetShowAll fenetre
  Gtk.main

Deux points méritent l’attention. L’extension OverloadedStrings permet d’écrire les titres directement entre guillemets, car GTK attend des chaînes Text et non des String. Et surtout, Gtk.main est une boucle bloquante : c’est elle qui attend les clics, les frappes et les redessins. Tant qu’on ne la quitte pas, la fenêtre reste vivante.

Fermer proprement l’application

En l’état, cliquer sur la croix ferme la fenêtre mais ne rend pas la main au programme. Il faut connecter le signal destroy de la fenêtre à l’arrêt de la boucle GTK.

import Data.GI.Base (on)

_ <- on fenetre #destroy Gtk.mainQuit

La fonction on est le pont central du modèle événementiel : elle relie un signal émis par un widget à une action Haskell. Ici, quand la fenêtre émet destroy, on appelle mainQuit, ce qui fait retourner Gtk.main et termine main.

Ajouter un bouton et un callback

Passons à l’essentiel : réagir à une interaction. On crée un bouton, on le place dans la fenêtre, puis on branche son signal clicked sur une fonction.

bouton <- Gtk.buttonNewWithLabel "Cliquez-moi"
Gtk.containerAdd fenetre bouton

_ <- on bouton #clicked $ do
  putStrLn "Le bouton a été cliqué !"
  Gtk.setButtonLabel bouton "Merci !"

Gtk.widgetShowAll fenetre

À chaque clic, le callback s’exécute : il affiche un message dans la console et change le texte du bouton. C’est exactement le principe de GTK — on décrit quoi faire quand tel événement survient, et la boucle principale se charge d’appeler nos fonctions au bon moment. Notez que containerAdd n’accepte qu’un seul enfant direct ; pour disposer plusieurs widgets, on utilise un conteneur comme Gtk.boxNew.

Le même code en style gtk2hs

Si vous tombez sur du code utilisant l’ancien binding gtk2hs, la structure est très proche, avec une syntaxe légèrement différente pour connecter les signaux :

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI
  fenetre <- windowNew
  bouton  <- buttonNewWithLabel "Cliquez-moi"
  containerAdd fenetre bouton
  on bouton buttonActivated $ putStrLn "Cliqué !"
  on fenetre objectDestroy   mainQuit
  widgetShowAll fenetre
  mainGUI

On retrouve les mêmes idées : initGUI pour démarrer, la connexion des signaux avec on, et mainGUI comme boucle bloquante. Seuls les noms changent.

Compiler et lancer

Avec Stack, il suffit d’un stack run ; avec Cabal, d’un cabal run. Une petite fenêtre apparaît, et chaque clic déclenche votre callback. Vous tenez là toutes les briques fondamentales d’une GUI GTK : fenêtre, widget, signal et boucle d’événements.

Aller plus loin

À partir de ce socle, on ajoute des champs de saisie (Gtk.entryNew), des dispositions en boîtes ou en grilles, et l’on peut même charger l’interface depuis un fichier décrit avec l’éditeur Glade. La force de Haskell reste entière : la logique métier demeure pure et typée, seule la couche d’interaction touche à IO.

À lire ensuite