Cross-platform C SDK logo

Cross-platform C SDK

Hello World!

❮ Back
Next ❯
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.

Once upon a time, there was a company called Taligent. Taligent was created by IBM and Apple to develop a set of tools and libraries like Cocoa. About the time Taligent reached the peak of its mindshare, Aaron met one of its engineers at a trade show and asked him to create a simple application: A window appears with a button. When the button is clicked, the words "Hello, World!" appear in a text field. The engineer created a project and started subclassing madly, subclassing the window and the button and the event handler. Then he started generating code: dozens of lines to get the button and the text field onto the window. After 45 minutes, he was still trying to get the app to work. A couple of years later, Taligent quietly closed its doors forever. Hillegass, Preble & Chandler - Cocoa Programming for OSX.


There is little we can say about the meaning of the Hello World! program every time we are faced with a new technology or programming methodology. So, let's get down to business.

Hello World! running on windows 10. Hello World! running on Windows XP.
Figure 1: Windows 10 y Windows XP.
Hello World! running on macOS Mojave. Hello World! running on macOS Leopard.
Figure 2: macOS 10.14 Mojave and MacOSX 10.6 Snow Leopard.
Hello World! running on Ubuntu. Hello World! running on Raspbian.
Figure 3: GTK+3 Ambiance (Ubuntu) and Adwaita Dark (Raspbian).

1. The complete program

Listing 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 "osmain.h"
osmain(i_create, i_destroy, "", App)

2. The skeleton

A NAppGUI application starts at osmain, a cross-platform macro that unifies the startup of a desktop program under different systems. It is defined in #include "osmain.h" and will receive four parameters: constructor, destructor, arguments (char_t), and the object type. In this way, any basic skeleton looks like this:

 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 "osmain.h"
osmain(i_create, i_destroy, "", App)

The #include "nappgui.h" directive, includes much of NAppGUI with a single statement. If you prefer, you can choose to include the headers separately as needed. In this case, we should replace a single #include with eleven. In the Reference Manual, it is indicated which header to include according to the function module that we are going to use.

 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. The constructor

The first parameter of osmain is the application constructor. As soon as the program starts, certain internal structures must be initialized, as well as starting the message loop inherent to all desktop applications. When everything is ready, the constructor will be called to create the application object. This object can be of any type and does not need to be derived from any class Application or similar, we are in C ;-). Because of the simplicity of this example, the application object contains only one window.

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. The main panel

To create the main window, we need the main panel, a container that integrates all the interface controls displayed in the window. The space inside the panel is organized in an invisible grid called Layout. Each panel can have several layouts and switch between them, but at least one is necessary. Within its cells we will locate the different widgets.

 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. The destructor

When the application terminates, osmain will call the destructor (second parameter of the macro) to free the application object and everything that depends on it, in order to perform a clean exit of the program. We will put a lot of emphasis on this, as failure to properly free all memory will be considered a serious programming error.

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

6. Launch the window

By default, NAppGUI creates all windows in hidden mode, so you need to display them explicitly. We establish a title, an initial position and launch it with window_show. We observe that in this first version our window is not very aesthetically pleasing (Figure 4). We will format it in a moment.

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);
   ...
}
Window with the controls very compressed since no formatting has been applied yet.
Figure 4: First version of Hello, World! (without format).

7. Layout format

To improve the appearance of our window, let's format the layout a bit. Specifically, we are going to set a column width and a height for the third row (text control). Then we will leave a margin on the edge and a separation between rows. (Figure 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);
Interface window properly formatted.
Figure 5: Hello World! after Layout formatting.

8. Exiting the program

When we press the button to close the main window, the program does not finish its execution. This is typical of macOS applications, where they still continue running in the Dock even if there is no window open. NAppGUI follows the same criterion of not closing the program, so we must make an explicit call to the osapp_finish function. To do this, we will capture the window_OnClose event using the listener macro.

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. Button Events

Finally, we'll catch the click event of the button and print a message in the text box each time it's clicked. We are going to implement the i_OnButton handler, responsible for composing and displaying the message, and connect it to the Button control we created earlier.

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));
An event is an action that occurs during the execution of the program. The operating system captures it and sends it to us through its callback(defined in listener()). More at Events.
❮ Back
Next ❯