SDK Multiplataforma en C logo

SDK Multiplataforma en C

¡Hola Mundo!

❮ Anterior
Siguiente ❯

Érase una vez una compañía llamada Taligent. Taligent fue creada por IBM y Apple para desarrollar un conjunto de herramientas y bibliotecas como Cocoa. En el momento en que Taligent alcanzó cierto grado de madurez, Aaron conoció a uno de sus ingenieros en una feria comercial y le pidió que creara una aplicación simple: Una ventana con un botón. Cuando se hace clic en el botón, las palabras "¡Hola mundo!" Aparecen en un campo de texto. El ingeniero creó un proyecto y comenzó a crear subclases locamente tanto para la ventana como para el botón y el controlador de eventos. Luego comenzó a generar código: docenas de líneas para obtener el botón y el campo de texto en la ventana. Después de 45 minutos, todavía estaba intentando que la aplicación funcionara. Un par de años después, Taligent cerró silenciosamente sus puertas para siempre. Hillegass, Preble & Chandler - Cocoa Programming for OSX.


Poco podemos decir del significado del programa Hello World! cada vez que nos enfrentamos a una nueva tecnología o metodología de programación. Por lo tanto, vamos al grano.

Hello World! corriendo en Windows 10. Hello World! corriendo en WindowsXP.
Figura 1: Windows 10 y WindowsXP.
Hello World! corriendo en macOS Mojave. Hello World! corriendo en macOS Leopard.
Figura 2: macOS 10.14 Mojave y MacOSX 10.6 Snow Leopard.
Hello World! corriendo en Ubuntu. Hello World! corriendo en Raspbian.
Figura 3: GTK+3 Ambiance (Ubuntu) y Adwaita Dark (Raspbian).

1. El programa completo

Listado 1: demo/hello/main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/* NAppGUI Hello World */

#include <nappgui.h>

typedef struct _app_t App;

struct _app_t
{
    Window *window;
    TextView *text;
    uint32_t clicks;
};

/*---------------------------------------------------------------------------*/

static void i_OnButton(App *app, Event *e)
{
    textview_printf(app->text, "Button click (%d)\n", app->clicks);
    app->clicks += 1;
    unref(e);
}

/*---------------------------------------------------------------------------*/

static Panel *i_panel(App *app)
{
    Panel *panel = panel_create();
    Layout *layout = layout_create(1, 3);
    Label *label = label_create();
    Button *button = button_push();
    TextView *text = textview_create();
    app->text = text;
    label_text(label, "Hello!, I'm a label");
    button_text(button, "Click Me!");
    button_OnClick(button, listener(app, i_OnButton, App));
    layout_label(layout, label, 0, 0);
    layout_button(layout, button, 0, 1);
    layout_textview(layout, text, 0, 2);
    layout_hsize(layout, 0, 250);
    layout_vsize(layout, 2, 100);
    layout_margin(layout, 5);
    layout_vmargin(layout, 0, 5);
    layout_vmargin(layout, 1, 5);
    panel_layout(panel, layout);
    return panel;
}

/*---------------------------------------------------------------------------*/

static void i_OnClose(App *app, Event *e)
{
    osapp_finish();
    unref(app);
    unref(e);
}

/*---------------------------------------------------------------------------*/

static App *i_create(void)
{
    App *app = heap_new0(App);
    Panel *panel = i_panel(app);
    app->window = window_create(ekWINDOW_STD);
    window_panel(app->window, panel);
    window_title(app->window, "Hello, World!");
    window_origin(app->window, v2df(500, 200));
    window_OnClose(app->window, listener(app, i_OnClose, App));
    window_show(app->window);
    return app;
}

/*---------------------------------------------------------------------------*/

static void i_destroy(App **app)
{
    window_destroy(&(*app)->window);
    heap_delete(app, App);
}

/*---------------------------------------------------------------------------*/

#include <osapp/osmain.h>
osmain(i_create, i_destroy, "", App)

2. El esqueleto

Una aplicación NAppGUI comienza en osmain, una macro multiplataforma que unifica la iniciación de un programa de escritorio bajo diferentes sistemas. Está definida en #include <osapp/osmain.h> y recibirá cuatro parámetros: constructor, destructor, argumentos (char_t) y el tipo de objeto. De esta forma, cualquier esqueleto básico se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include "nappgui.h"

typedef struct _app_t App;
struct _app_t
{
    Window *window;
};

static App *i_create(void)
{
    App *app = heap_new0(App);
    return app;
}

static void i_destroy(App **app)
{
    heap_delete(app, App);
}

#include <osapp/osmain.h>
osmain(i_create, i_destroy, "", App)

La directiva #include "nappgui.h", incluye gran parte de NAppGUI con una sola instrucción. Si lo prefieres, puedes optar por incluir las cabeceras por separado según sea necesario. En este caso, deberíamos reemplazar un solo #include por once. En el Manual de Referencia, se indica que cabecera incluir según el módulo de funciones que vayamos a utilizar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <gui/button.h>
#include <gui/gui.h>
#include <gui/label.h>
#include <gui/layout.h>
#include <gui/panel.h>
#include <gui/textview.h>
#include <gui/window.h>
#include <geom2d/v2d.h>
#include <core/event.h>
#include <core/heap.h>
#include <core/strings.h>

3. El constructor

El primer parámetro de osmain es el constructor de la aplicación. Tan pronto como se inicia el programa, ciertas estructuras internas deben inicializarse, así como arrancar el ciclo de mensajes inherente a todas las aplicaciones de escritorio. Cuando todo esté listo, se llamará al constructor para crear el objeto aplicación. Este objeto puede ser de cualquier tipo y no necesita derivarse de ningún class Application o similar, estamos en C ;-). Dada la sencillez de este ejemplo, el objeto aplicación tan solo contiene una ventana.

1
2
3
4
5
6
7
8
static App *i_create(void)
{
    App *app = heap_new0(App);
    Panel *panel = i_panel(app);
    app->window = window_create(ekWINDOW_STD);
    window_panel(app->window, panel);
    return app;
}

4. El panel principal

Para crear la ventana principal, necesitamos el panel principal, un contenedor que integra todos los controles de interfaz que se muestran en la ventana. El espacio dentro del panel está organizado en una cuadrícula invisible llamada Layout. Cada panel puede tener varios layouts y alternar entre ellos, pero al menos uno es necesario. Dentro de sus celdas ubicaremos los diferentes controles de interfaz.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static Panel *i_panel(App *app)
{
    Panel *panel = panel_create();
    Layout *layout = layout_create(1, 3);
    Label *label = label_create();
    Button *button = button_push();
    TextView *text = textview_create();
    label_text(label, "Hello!, I'm a label");
    button_text(button, "Click Me!");
    layout_label(layout, label, 0, 0);
    layout_button(layout, button, 0, 1);
    layout_textview(layout, text, 0, 2);
    panel_layout(panel, layout);
    return panel;
}

5. El destructor

Cuando la aplicación termine, osmain llamará al destructor (segundo parámetro de la macro) para liberar el objeto aplicación y todo lo que dependa de él, con el fin de realizar una salida limpia del programa. Pondremos mucho énfasis en esto, ya que el hecho de no liberar correctamente toda la memoria se considerará un grave error de programación.

1
2
3
4
5
static void i_destroy(App **app)
{
    window_destroy(&(*app)->window);
    heap_delete(app, App);
}

6. Lanzar la ventana

Por defecto, NAppGUI crea todas las ventanas en modo oculto, por lo que es necesario visualizarlas explícitamente. Establecemos un título, una posición inicial y la lanzamos con window_show. Observamos que en esta primera versión nuestra ventana no queda muy estética (Figura 4). En un momento le daremos formato.

1
2
3
4
5
6
7
8
static App *i_create(void)
{
   ...
   window_title(app->main_window, "Hello World!");
   window_origin(app->main_window, v2df(500, 200));
   window_show(app->main_window);
   ...
}
Ventana con los controles muy comprimidos ya que aún no se ha aplicado ningún formato.
Figura 4: Primera versión de ¡Hola, Mundo! (sin formato).

7. Formato del Layout

Para mejorar la apariencia de nuestra ventana, vamos a darle un poco de formato al diseño. Concretamente, vamos a establecer un ancho de columna y un alto para la tercera fila (control de texto). Después dejaremos un margen en el borde y una separación entre filas. (Figura 5).

1
2
3
4
5
layout_hsize(layout, 0, 200);
layout_vsize(layout, 2, 100);
layout_margin(layout, 5);
layout_vmargin(layout, 0, 5);
layout_vmargin(layout, 1, 5);
Ventana de interfaz debidamente formateada.
Figura 5: ¡Hola, Mundo! tras el formateo del Layout.

8. Saliendo del programa

Cuando presionamos el botón de cerrar la ventana principal, el programa no finaliza su ejecución. Esto es típico de las aplicaciones macOS, donde aún siguen corriendo en el Dock aunque no haya ninguna ventana abierta. NAppGUI sigue el mismo criterio de no cerrar el programa, por lo que debemos realizar una llamada explícita a la función osapp_finish. Para ello, capturaremos el evento window_OnClose mediante la macro listener.

1
2
3
4
5
6
7
8
9
static void i_OnClose(App *app, Event *e)
{
    osapp_finish();
}

static App *i_create(void)
{
   window_OnClose(app->main_window, listener(app, i_OnClose, App));
}

9. Eventos Button

Finalmente, capturaremos el evento clic del botón e imprimiremos un mensaje en el cuadro de texto cada vez que se pulse. Vamos a implementar el manejador i_OnButton, responsable de componer y mostrar el mensaje y lo conectaremos al control Button que hemos creado anteriormente.

1
2
3
4
5
6
7
8
static void i_OnButton(App *app, Event *e)
{
    textview_printf(app->text, "Button click (%d)\n", app->clicks);
    app->clicks += 1;
    unref(e);
}
...
button_OnClick(button, listener(app, i_OnButton, App));
Un evento es una acción que ocurre durante la ejecución del programa. El sistema operativo lo captura y nos lo envía a través de su manejador (definido en listener()). Más en Eventos.
❮ Anterior
Siguiente ❯