GUIs con Haskell en entornos Windows (I)

Haskell y GTK+. UIs en Windows
Es dificil encontrar librerias en Haskell para desarrollar GUIs y aun mas dificil de que estas funcionen en entornos Windows. En esta serie de articulos explicare mi stack Haskell/GTK+ para desarrollar aplicaciones graficas para esta plataforma.
Instalacion
Para poder usar components GTK en Windows con Haskell necesitaremos de la ayuda de MSYS2 y evidentemente una instalacion funcional de Haskell, mas vuestro editor favorito.
MSYS2 es una capa de compatibilidad POSIX para Windows que provee, entre otras cosas, una shell bash, autotools y toolchains para generar aplicaciones en entornos Windows con MinGW.
Una vez instalado MSYS2 abriremos una shell y actualizaremos el entorno
pacman -Syu
Despues instalaremos las herramientas necesarias
pacman -S mingw64/mingw-w64-x86_64-pkg-config mingw64/mingw-w64-x86_64-gobject-introspection mingw64/mingw-w64-x86_64-gtksourceview3
Es posible que en vuestros equipos necesiteis permisos de Administrador para realizar estas operaciones por lo que si no os funciona con permisos normales debereis abrir la shell con permisos de Administrador.
Tambien es posible que si usais Stack mas adelante os de errores relacionados con libpng16-16.dll o con el GIR de libcairo por lo que si os ocurre esto debeis copiar la version de zlib1.dll que teneis en vuestra instalacion de MSYS2 (carpeta /bin) a la instalacion de stack (carpeta x86_64-windows).
Si no sabeis donde esta dicha carpeta la podeis localizar ejecuntando el siguiente comando
stack path --programs
Por ultimo debeis habilitar las siguientes variables de entorno
SET PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%
SET PKG_CONFIG_PATH=C:\msys64\mingw64\lib\pkgconfig
SET XDG_DATA_DIRS=C:\msys64\mingw64\share
haskell-gi y familia
Para el resto de la serie nos centraremos en el paquete haskell-gi ( http://hackage.haskell.org/package/haskell-gi ) y paquetes relacionados.
En la pagina github del proyecto advierten de un bug con las versiones 8.2.x de ghc.
Existen bindings para la mayoria de librerias principales que usan gobject-introspection y en la pagina del proyecto hay una buena cantidad de ejemplos.
Ejemplo Basico
Para mostraros el ejemplo basico recurrire al ejemplo que se provee en la pagina del proyecto
{-# LANGUAGE OverloadedStrings, OverloadedLabels #-}
module Main where
import qualified GI.Gtk as Gtk
import Data.GI.Base
main :: IO ()
main = do
Gtk.init Nothing
win <- new Gtk.Window [ #title := "Hi there" ]
on win #destroy Gtk.mainQuit
button <- new Gtk.Button [ #label := "Click me" ]
on button #clicked (set button [ #sensitive := False,
#label := "Thanks for clicking me" ])
#add win button
#showAll win
Gtk.main
Nuestro cabal queda como sigue
cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.31.1.
--
-- see: https://github.com/sol/hpack
--
-- hash: 9907894b3ebabf9fb67d9148d9b120e2dfc67ffede662a2a2381d4037c11aa33
name: helloblog
version: 0.1.0.0
description: Please see the README on GitHub at <https://github.com/githubuser/helloworld#readme>
homepage: https://github.com/githubuser/helloworld#readme
bug-reports: https://github.com/githubuser/helloworld/issues
author: Author name here
maintainer: example@example.com
copyright: 2020 Author name here
license: BSD3
license-file: LICENSE
build-type: Simple
extra-source-files:
README.md
ChangeLog.md
executable helloblog-exe
main-is: Main.hs
other-modules:
Paths_helloblog
hs-source-dirs:
app
ghc-options: -threaded -rtsopts -optl-mwindows -with-rtsopts=-N
build-depends:
base
, haskell-gi-base
, gi-gtk
default-language: Haskell2010
Y el solver de nuestro stack apunta a lts-14.3. Muy importante la opcion -optl-mwindows.
Hacemos un stack run y tenemos nuestro ejemplo funcionando.
El Codigo
Despues de realizar los imports y establecer las extensiones que vamos a usar lo primero que debemos hacer es inicializar Gtk a traves de la funcion init.
init recibe un parametro y tiene la siguiente firma
init :: (HasCallStack, MonadIO m) => m ()
Si vamos a usar Application no es necesario llamar a init y en su lugar usaremos Application::startup
Despues pasamos a crear una ventana llamando a la funcion new cuya firma es
new :: MonadIO m => (ManagedPtr a -> a) -> [AttrOp a tag] -> m a
y que esta definida en el modulo Data.GI.Base.Constructible. A esta funcion le pasamos lo que queremos construir (Gtk.Window en este caso, su unico constructor) y una lista con los atributos que vamos a utilizar para inicializar dicho objeto. Practicamente todos los componentes GTK implementan la clase ManagedPtr que es un envoltorio para la clase ForeignPtr, que se encarga de gestionar referencias a objetos creados con lenguajes ajenos a Haskell.
La ventana retornada viene envuelta en un MonadIO.
win <- new Gtk.Window [ #title := "Hi there" ]
A continuacion asignamos una manejador de señales al evento destroy de la ventana recien creada
on win #destroy Gtk.mainQuit
Para ello se necesita habilitar las extension OverloadedLabels, cosa que se hizo al inicio del fichero
{-# LANGUAGE OverloadedStrings, OverloadedLabels #-}
y que se encarga de realizar la traslacion de las llamadas al API C de Gtk, basicamente sustituyendo # por el on de los eventos, el prefijo gtk, gdk.. en las llamadas a funcion entre otras cosas.
El evento previo tambien podriamos escribirlo como
Gtk.onWidgetDestroy win $ do Gtk.mainQuit
Donde Gtk.onWidgetDestroy es el evento (en el caso previo era #destroy), win es la instancia y el do es el manejador propiamente dicho.
Tambien creamos un boton y asignamos un manejador al evento onButtonClicked
button <- new Gtk.Button [ #label := "Click me" ]
on button #clicked (set button [ #sensitive := False, #label := "Thanks for clicking me" ])
donde en el manejador le decimos si esta habilitado (#sensitive) y le asignamos un nuevo texto (#label).
Esto mismo tambien se puede hacer de la misma forma alternativa que hemos visto antes
Gtk.onButtonClicked button $ do button `set` [ #sensitive := False, #label := "Thanks for clicking me" ]
donde, en este caso, utilizamos set como operador en lugar de usarlo como funcion.
#add win button
#showAll win
Gtk.main
#add llama al metodo GtkContainer::add para añadir el boton a la ventana, a cualquier contenedor en realidad, #showAll muestra la ventana llamando al metodo GtkWidget::showAll y por ultimo entramos en el loop principal de Gtk.
Conclusiones y Proximos Pasos
Hemos visto como preparar nuestro sistema Windows para desarrollar con Haskell y Gtk y hemos visto el programa de ejemplo disponible en la pagina del proyecto de haskell-gi.
En nuestra proxima entrega veremos ejemplos algo mas complejos de componentes, trabajando los layouts.
El ejemplo lo podreis encontrar en https://github.com/juanan-martin-lpz/haskell-helloblog.
Un saludo y hasta la proxima.