GUIs con Haskell en entornos Windows (I)

8 febrero 2020 0 Por Juan Martin

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.