Crear ventanas y botones en Rust con GTK

Cuando salió el Macintosh allá por 1984, fue novedosa su interfaz gráfica. En efecto, fue la primera interfaz gráfica popular y ha servido de inspiración para muchos otros sistemas de ventanas.

Hoy vamos a introducir una manera de crear interfaces gráficas de usuario (GUI) con Rust. Para ello usaremos GTK. GTK funciona sobre GNU/Linux, macOS y Windows (aunque es un poco más complicado de lo que debiera).

Para ello usaremos la fantástica crate gtk del proyecto Gtk-rs.

Instalando gtk

Añade al fichero Cargo.toml las siguiente líneas:

Y ejecuta cargo build.

Creando una ventana

Lo primero que vamos a hacer es crear una ventana.

Importamos gtk e iniciamos GTK.

Ahora podemos crear una ventana. GtkWindow es un struct con métodos asociados, por lo que podemos ajustar ciertos parámetros. En este código parece que estamos usando orientación a objetos, pero recuerdo que Rust técnicamente no tiene clases ni herencia.

Ahora vamos a añadir un evento para que cuando se pulse la X en nuestra ventana, se cierre el programa. Para ello tenemos que entender como funciona GTK. Cuando programamos en GTK lo que hacemos es configurar la aplicación y posteriormente ceder la ejecución a una función gtk_main que procesa todos los eventos según haya sido configurado. Para configurar los eventos usaremos callbacks, que en Rust se pueden implementar con closures. Las funciones que en GTK nos permiten conectar eventos siempre tienen el prefijo connect.

Ahora vamos a mostrar la ventana. Por defecto en GTK todos los widgets (todo lo que se muestra en pantalla) está oculto. Para mostrar todos los widgets que se han añadido a la ventana se suele usar show_all, que va a haciendo show de forma recursiva. Por último, le damos el control de la aplicación a gtk::main.

Una vez hecho esto, si compilamos con cargo run ya deberíamos ver una preciosa ventana GTK.

Y por supuesto, si pulsamos la X, la aplicación se cierra.

Layouts y botones en GTK

Vamos ahora a añadir dos cosas: un botón y un label que nos de un número del dado. Para poner varios elementos en una aplicación GTK es recomendable usar un layout. Voy a usar el layout Box, que permite agrupar los widgets de forma vertical u horizontal. Para los números aleatorios voy a usar la crate rand. El código final sería así:

Aquí pasan varias cosas interesantes. La primera es que usamos add para ir añadiendo de forma jerárquica los widgets. Debajo de Window está Box y debajo de Box tenemos Button y Label.

Por otra parte, vemos que la función asociada al evento del click de los botones es connect_clicked. Bien, en este ejemplo he introducido algo importante. Estoy modificando un widget desde un evento relacionado a otro widget. ¿Esto como se lleva con las reglas de propiedad/ownership de Rust? Bastante mal. Rust no puede saber si cuando se ejecuta el evento tenemos acceso al widget en cuestión que vamos a modificar. Afortunadamente, la API de gtk-rs ha sido diseñada con esto en cuenta y simplemente podemos hacer una llamada a clone para obtener otra referencia al objeto, que podemos pasar al closure (con move). Este clonado no lo es tal, sino que hace uso de Rc. Simplemente se nos presenta de forma transparente.

El ejemplo final: dibujando con Cairo

En este último ejemplo voy a añadir un widget donde se podrá ver la cara del dado que ha salido. Para ello uso un GtkDrawingArea, que permite usar la API de Cairo.

Con esto tenemos lo básico para empezar a diseñar GUIs con GTK y Rust.

 

Programando para Haiku – BApplication, BWindow y BButton – Parte I

Hoy voy a comenzar una serie de tutoriales donde explicaré la programación de una apliación en Haiku. Para ello vamos a usar C++ y la BeAPI. Siempre que tengais cualquier duda podéis visitar la documentación oficial: tanto la antigua de BeOS en el BeBook y la nueva de Haiku en Haiku API. Aun así hay APIs nuevas que todavía no aparecen documentadas. En ese caso hay que recurrir al código fuente.

Librerías en Haiku

Haiku (y BeOS) comparte con UNIX muchas características de bajo nivel. El tema de las librerías es uno de ellos. También han de empezar por lib y terminar por .so si son compartidas y .a si son estáticas. Para compilar también se usa GCC. Sin embargo hay una pequeña diferencia con el resto de sistemas UNIX. En UNIX normalmente disponemos de una librería del C, libc y una librería de funciones matemáticas, libm. En Haiku no existe libm, en cambio existen muchas más, libroot y libbe, para interactuar con el sistema de manera básica, libtracker, con funciones relacionadas con el explorador de archivos, libnetwork y libnetapi, con funciones de red, y muchas otras.

Además la API se divide en Kits, cada Kit se encarga de una tareas diferentes dentro del sistema operativo. AppKit, Game Kit, Interface Kit, Media Kit, Storage Kit, etc… Si queremos usar la funcionalidad de un kit tendremos que revisar que hemos añadido la librería correcta al compilador y que hemos añadido #include dentro del código que lo usemos.

Un hola mundo

Vamos a empezar por lo simple, una aplicación que muestre una ventana y ya.

Creamos un archivo de C++, será el punto de inicio de nuestra aplicación. Como sabéis, el punto de inicio de un programa de C o C++ es la función main.

Hemos creado un objeto llamado app del tipo AplicacionPrueba y después hemos ejecutado la aplicación. AplicacionPrueba tiene que ser del tipo BApplication. Es la clase básica de todas las aplicaciones Haiku/BeOS. BApplication provee de mensajería entre los distintos procesos del programa (hay que tener en cuenta que BeOS se diseñó pensando en el multiproceso). Vamos a ver como definimos AplicacionPrueba

Las Application necesitan un MIME type, al igual que se usa para indicar los tipos de archivo. No es necesario que sea real. Además hemos creado un objeto VentanaPrueba y la mostramos. VentanaPrueba es del tipo BWindow y es la ventana básica de Haiku, lo que vemos. Veamos la definición:

BWindow necesita un tamaño, que es indicado con BRect, un título, un estilo (por defecto es BTITLEDWINDOW, pero podemos tener ventanas sin bordes o modal) y opciones varias. En las opciones varias podemos especificar que al cerrar la ventana se cierre la aplicación (BQUITONWINDOWCLOSE), que el usuario no pueda cambiar su tamaño (BNOTRESIZABLE), que no se pueda minimizar (BNOTMINIMIZABLE) y otras opciones por el estilo.

Además dentro de la clase hemos definido dos funciones virtuales, es decir, que tienen implementación por defecto de la clase padre, BWindow, pero nosotros podemos modificar su comportamiento.

QuitRequested es llamada cuando algo pide el cierre de la ventana. El objeto global beappmessenger es del tipo BApplication, pero está definido en todos los puntos de nuestra aplicación sin que nosotros hagamos nada. Gracias a este objeto podemos enviar mensajes entre procesos. En este caso enviamos el mensaje a la aplicación de BQUITREQUESTED. Y luego llamamos a la función sin modificar.

MessageReceived es muy importante. Se encarga de procesar todos los mensajes que recibe la ventana. Para distinguir los mensajes (que son del tipo BMessage) tenemos que inspeccionar la propiedad what. Se trata de un valor de tipo uint32. Hay algunos ya definidos por el sistema como BQUITREQUESTED pero nosotros podemos definir más. Veremos más tarde como. De momento simplemente devolvemos el procesado de mensajes a BWindow padre.

Con esto ya podemos compilar.

Añadiendo BView, BButton y BGroupLayout

Ahora vamos a añadir cosas a nuestra ventana sosa. Ponemos una vista dentro de la ventana. Las vistas en Haiku son muy potentes pero eso lo trataré en otro momento. A esa vista le añadiremos un botón.

Aquí hemos hecho varias cosas. Por una parte he creado un layout horizontal. Es decir, dispongo el espacio de la ventana de manera horizontal, según se vayan añadiendo elementos lo harán a la derecha. Esto no estaba en BeOS y es particular de Haiku, pero recomiendo usar este sistema pues permite realizar un responsive design. Creamos una vista o panel. Bounds() indica que cubra todo el espacio disponible. El resto son propiedades de la vista más o menos estándar. Con SetViewColor le podemos poner un color de fondo, y con SetLayout le aplicamos el layout previamente creado.

Creamos un botón, que es del tipo BButton. BButton tiene muchos constructores si revisais la documentación pero este es muy cómodo si usamos el sistema de layouts. Simplemente indicamos el texto que va a mostrar y el mensaje que envía. En este caso NULL pues no vamos a poner ninguno.

sizer->AddView() lo usamos para añadir el botón al layaout y AddChild para añadir la vista a la ventana. Puedes compilar.

Añadiendo eventos. Mensajería con BMessage. Diálogo con BAlert.

Vamos ahora a crear un evento para el botón. Cuando pulsemos el botón mostrará un mensaje al usuario.

Los eventos se realizan por el sistema de mensajería basado en BMessage y BHandler. Para crear un BMessage necesitamos un ID, que es del tipo uint32. Eso es lo mínimo y con eso ya serviría para este caso.

Pero los mensajes pueden llevar información adicional de cualquier tipo. Por ejemplo si queremos añadir además una cadena de texto al mensaje usaremos AddString.

Podremos recuperar el valor en cualquier momento con FindString.

Ahora si vamos a MessageReceived podemos añadir código que gestione este tipo de mensaje.

Con un simple case gestionamos el mensaje. Para mostrar un diálogo simple se puede usar BAlert. Es muy simple, indicamos el título, el contenido del mensaje y el texto del botón que aparecerá. Y con Go lo mostramos.

Esta ha sido la primera parte del tutorial. Os ha gustado. Hay algo que no haya quedado claro. Comentádmelo.