Cross-platform C SDK logo

Cross-platform C SDK

Create new application

❮ 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.

I consider myself a technical person who chose a great project and an excellent way to carry it out. Linus Torvalds.


In Build NAppGUI we have seen how to compile and package the SDK. Also, in Hello World!, we learned the basic structure of a NAppGUI-based application. The time has come to create our own applications, taking advantage of the CMake modules included in the /prj folder of the installation.

This chapter is focused on the use of CMake. If you use another build system in your projects, you will have to adapt the dependency management yourself.

1. Use of find_package()

NAppGUI supports the CMake find_package() command, so managing dependencies is extremely simple. To create a new desktop application, start in a new folder with two files: CMakeLists.txt and main.c:

Our first NAppGUI application.
1
2
3
4
12/15/23  04:20 PM    <DIR>          .
12/15/23  04:19 PM    <DIR>          ..
12/15/23  04:11 PM               292 CMakeLists.txt
12/15/23  03:57 PM             2,315 main.c
CMakeLists.txt
1
2
3
4
5
6
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
# Remove WIN32 in Linux and macOS
add_executable(naphello WIN32 main.c)
target_link_libraries(naphello ${NAPPGUI_LIBRARIES})
main.c. Hello World
 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
84
85
/* 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)
{
    String *msg = str_printf("Button click (%d)\n", app->clicks);
    textview_writef(app->text, tc(msg));
    str_destroy(&msg);
    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)

You can now generate and compile the solution, using CMake in the usual way. If you installed NAppGUI cmake --install in a specific location (parameter --prefix) you must indicate the same path using -DCMAKE_INSTALL_PREFIX.

 
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui
cmake --build build

The find_package() command knows how to locate a package within the usual system directories, depending on each platform. We will need to specify the prefix only when the package is installed in any alternate directory.

-DCMAKE_INSTALL_PREFIX does not imply priority in the search. find_package() might first find an installation in the system folders.

In the /build/Debug directory you will have the napphello executable (Figure 1).

NAppGUI Hello World application.
Figure 1: Result of compiling and running napphello.

2. NAppProject.cmake

While you can manage your project's CMakeLists.txt yourself, setting up a cross-platform desktop application can be a bit tedious (even for CMake). NAppGUI provides a number of modules within the /prj directory of the installation, which can simplify this task. To test it, create a new folder and add this single file CMakeLists.txt:

CMakeLists.txt
1
2
3
4
5
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)

We execute CMake in the same way as in the previous case:

 
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui
cmake --build build

In this case we have used the function nap_project_desktop_app() of the module NAppProject.cmake, which has created a new folder /hello. We open the Visual Studio solution that has been generated in /build (Figure 2).

Screenshot of VisualStudio Solution Explorer.
Figure 2: Solution created by NAppProject.cmake.
 
nap_project_desktop_app(appName path)
  • appName: The name of the application.
  • path: Relative path to CMakeLists.txt where the project will be located (in this case ./hello). Any route depth is supported. For example: games/myapp, demo/games/myapp, etc.

The first time this function is executed, several things are done:

  • A new directory hello has been created with a default desktop application napphello.c and a CMakeLists.txt.
  • A folder hello/res has been created with an image, and it has been used as an icon for the application (Figure 3). In Resources we will continue going deeper.
  • The newly created /hello/CMakeLists.txt has automatically linked to the NAppGUI binaries.

Successive calls to CMake will not overwrite the project files, so we can edit them without fear of losing the changes. Once the project is created, nap_project_desktop_app() will simply call add_subdirectory(). The nap_desktop_app() command in /hello/CMakeLists.txt knows how to handle cross-platform quirks. For example, in the case of macOS it will create a bundle instead of an isolated executable.

NAppGUI Hello World application.
Figure 3: NAppHello with app icon.

We don't have to limit ourselves to a single application. Our solution will support numerous targets. For example add this line to CMakeLists.txt and rerun cmake -S . -B build.

CMakeLists.txt
1
2
3
4
5
6
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)
nap_project_desktop_app(nappbye bye)

If the solution was already open, it is possible that the IDE will notify you that there have been changes (Figure 4). After pressing [Reload], you will see that the new project (Figure 5) has appeared.

Warning displayed by Visual Studio when detecting that new files have been added.
Figure 4: Notice of changes in Visual Studio.
Screenshot of VisualStudio Solution Explorer with new changes to the solution.
Figure 5: Updated solution, with the new nappbye project.

3. Add files

Going back to the napphello project, we see that by default only one source code file (napphello.c) is created that contains the entire application. You will most likely want to split the code between different files. Create a pair of new files /hello/myfunc.c and /hello/myfunc.h from the IDE or directly from the browser. Open them and add these lines:

/hello/myfunc.h
1
2
3
4
5
// Example of new header

#include <core/core.hxx>

real32_t myadd_func(real32_t a, real32_t b);
/hello/myfunc.c
1
2
3
4
5
6
7
8
// Example of new c file

#include "myfunc.h"

real32_t myadd_func(real32_t a, real32_t b)
{
    return a + b;
}

Abre /hello/napphello.c y edita la función i_OnButton.

/hello/napphello.c
1
2
3
4
5
6
7
8
9
...
static void i_OnButton(App *app, Event *e)
{
    real32_t res = myadd_func(56.4f, 23.3f);
    textview_printf(app->text, "Button click (%d-%.2f)\n", app->clicks, res);
    app->clicks += 1;
    unref(e);
}
...

Re-generate the solution with cmake -S . -B build. The IDE, Visual Studio in this case, informs us again that there have been changes in the napphello project. Simply press [Reload All] as we did in the previous case.

Recompile and run napphello to see the changes you just made. You can create as many files and subfolders within the /hello directory as you need to better organize your code. Always remember to run cmake -S . -B build every time you add or remove files from the project. The nap_desktop_app() command will update the solution by "cloning" the directory structure within the project (napphello in this case).

At this point we recommend that you spend some time researching, compiling and testing the examples in the demo folder within the NAppGUI repository.

4. Command line applications

Similar to the desktop applications seen above, it is possible to create console applications. Add this new line to the CMakeLists.txt of the solution.

CMakeLists.txt
1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)
nap_project_desktop_app(nappbye bye)
nap_project_command_app(myutil utils/myutil)

When regenerating the solution with cmake -S . -B build, Visual Studio will alert you again that you need to reload the solution. A new project will have been created in ./utils/myutil (Figure 6), but this time if you compile and run it no window will appear. You will only see a message in the Visual Studio console:

1
Hello world!
Screenshot of VisualStudio Solution Explorer with three projects.
Figure 6: Solution with the three executables (targets).

If you open myutil.c you will find the code that generated the previous output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* NAppGUI Console Application */

#include <core/coreall.h>

int main(int argc, char *argv[])
{
    unref(argc);
    unref(argv);
    core_start();
    bstd_printf("Hello world!\n");
    core_finish();
    return 0;
}

Which is the typical template of a C program, to which the support of the core library has been included. From here, we can modify the code and compile. nap_command_app() already set everything up for us.

 
nap_project_command_app(appName path)
  • appName: The name of the application.
  • path: Relative path to . where the project will be located (in this case ./utils/myutil).

It goes without saying that the behavior of nap_project_command_app() is identical to that of nap_project_desktop_app(). It will not overwrite the project files once created and will integrate all new files that we add in the future.


5. Use of libraries

Let's imagine that our three applications needed to share certain functionalities. The smartest thing would be to encapsulate these functions in a library and for all three to have access to them. We will achieve this by inserting a new line in our CMakeLists.txt:

CMakeLists.txt
1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_library(common common)
nap_project_desktop_app(napphello hello)
nap_project_desktop_app(nappbye bye)
nap_project_command_app(myutil utils/myutil)

Notice that the command we have inserted nap_project_library() precedes the applications. This is because CMake needs to process dependencies before the projects that use them.

 
nap_project_library(libName path)
  • libName: The name of the library.
  • path: Relative path to CMakeLists.txt where the project will be located (in this case ./common).

As with application projects, the first time nap_project_library() runs, a series of default files are created. Later they can be edited, deleted or added more as we have just seen in the case of applications:

  • common.def: File that defines the _common_api macro necessary for the export of symbols. More information in Symbols and visibility.
  • common.hxx: Here we will include the definitions of public types, such as enum, struct. At the moment common does not contain public types.
  • common.h: Header file. Here we will write the general function declaration of the library. By default, CMake creates two: common_start() and common_finish(), where we would implement global start and end code for the library, if necessary.
  • common.c: Implementation of general functions.
  • CMakeLists.txt: Where the nap_library() command is used, analogous to nap_desktop_app() which will handle the particularities of each platform.

Open common.h and common.c, adding a new function:

common.h
1
_common_api uint32_t common_add(uint32_t a, uint32_t b);
common.c
1
2
3
4
uint32_t common_add(uint32_t a, uint32_t b)
{
    return a + b;
}

Edit the command nap_desktop_app() in /hello/CMakeLists.txt, and include the dependency with common:

/hello/CMakeLists.txt
1
nap_desktop_app(napphello "common" NRC_NONE)

Run cmake -S -B build again for all changes to take effect. You can now use the new common_add function within napphello.c:

napphello.c
1
2
3
4
5
6
7
static void i_OnButton(App *app, Event *e)
{
    uint32_t r = common_add(100, 200);
    textview_printf(app->text, "Button click (%d-%d)\n", app->clicks, r);
    app->clicks += 1;
    unref(e);
}

You can create as many libraries as your project needs. The only thing you should keep in mind is to include the name of the new dependency in the commands nap_desktop_app(), nap_command_app() or nap_library() of each target. At Create new library we will continue to delve deeper into the use of libraries.


6. C/C++ Standard

Compilers generally allow you to check that code conforms to certain C/C++ standards, issuing warnings or errors when it does not. For the sake of portability, all projects generated by nap_desktop_app(), nap_command_app() and nap_library() set the older standards (C90 and C++98 respectively). You may want to use more modern standards in your projects. Open /hello/CMakeLists.txt and add these two lines:

1
2
3
4
nap_desktop_app(napphello "" NRC_NONE)
nap_target_c_standard(napphello 11)
nap_target_cxx_standard(napphello 14)
target_link_libraries(napphello ${NAPPGUI_LIBRARIES})

The command nap_target_c_standard() has set the standard C11 for napphello. Similarly, nap_target_cxx_standard() has selected C++14.

  • C Standard: 90, 99, 11, 17 y 23.
  • C++ Standard: 98, 11, 14, 17, 20, 23 y 26.
If CMake or the compiler does not support the indicated standard, the highest allowed will be established. It is the responsibility of the programmer to use the appropriate compilers for the chosen standard.

7. NAppCompilers.cmake

In the same way that the NAppProject.cmake module helps us create and configure our own projects, NAppCompilers.cmake does the same with compilers. To use it in your project, you will only have to add these two lines to your main CMakeLists.txt.

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppCompilers.cmake")
nap_config_compiler()
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)

The function nap_config_compiler() detects the compiler among all those supported by NAppGUI: MSVC, GCC, Clang and AppleClang, setting the typical options for each configuration and platform. It also adds support for the different CMake configuration options that NAppGUI uses in its own libraries and example applications. More information at Generators, compilers and IDEs.

❮ Back
Next ❯