Crear nueva aplicación
Me considero una persona técnica que eligió un gran proyecto y una excelente manera para llevarlo a cabo. Linus Torvalds.
En Compilar NAppGUI hemos visto como compilar y empaquetar el SDK. También, en ¡Hola Mundo!, aprendimos la estructura básica de una aplicación basada en NAppGUI. Ha llegado el momento de crear nuestras propias aplicaciones, aprovechando los módulos CMake incluidos en la carpeta /prj
de la instalación.
Este capítulo está enfocado en el uso de CMake. Si utilizas otro build system en tus proyectos, deberás adaptar tu mismo la gestión de dependencias.
1. Uso de find_package()
NAppGUI soporta el comando find_package()
de CMake, por lo que gestionar las dependencias es sumamente sencillo. Para crear una nueva aplicación de escritorio, comienza en una nueva carpeta con dos archivos: CMakeLists.txt
y main.c
:
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 |
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}) |
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) |
Ya puedes generar y compilar la solución, utilizando CMake de la forma habitual. Si instalaste NAppGUI cmake --install
en una ubicación específica (parámetro --prefix
) deberás indicar la misma ruta mediante -DCMAKE_INSTALL_PREFIX
.
|
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui cmake --build build |
El comando find_package()
sabe como localizar un paquete dentro de los directorios habituales del sistema, en función de cada plataforma. Necesitaremos especificar el prefijo únicamente cuando el paquete esté instalado en cualquier directorio alternativo.
-DCMAKE_INSTALL_PREFIX no implica prioridad en la búsqueda. find_package()
podría encontrar primero una instalación en las carpetas del sistema.
En el directorio /build/Debug
tendrás el ejecutable napphello
(Figura 1).
2. NAppProject.cmake
Si bien puedes gestionar tu mismo el CMakeLists.txt
de tu proyecto, configurar una aplicación de escritorio multiplataforma puede resultar algo tedioso (incluso para CMake). NAppGUI proporciona una serie de módulos dentro del directorio /prj
de la instalación, que pueden simplificar esta tarea. Para probarlo, crea una nueva carpeta y añade este único fichero 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) |
Ejecutamos de CMake de la misma manera que en el caso anterior:
|
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui cmake --build build |
En este caso hemos utilizado la función nap_project_desktop_app()
del módulo NAppProject.cmake
, que ha creado una nueva carpeta /hello
. Abrimos la solución de Visual Studio que se ha generado en /build
(Figura 2).
|
nap_project_desktop_app(appName path) |
appName
: El nombre de la aplicación.path
: Ruta relativa alCMakeLists.txt
donde se ubicará el proyecto (en este caso./hello
). Se admite cualquier profundidad de ruta. Por ejemplo:games/myapp
,demo/games/myapp
, etc.
La primera vez que se ejecuta esta función se realizan varias cosas:
- Se ha creado un nuevo directorio
hello
con una aplicación de escritorio por defectonapphello.c
y unCMakeLists.txt
. - Se ha creado una carpeta
hello/res
con una imagen, y se ha utilizado como icono de la aplicación (Figura 3). En Recursos seguiremos profundizando. - El
/hello/CMakeLists.txt
recién creado, ha enlazado automáticamente con los binarios de NAppGUI.
Sucesivas llamadas a CMake no sobrescribirán los archivos del proyecto, por lo que podemos editarlos sin temor a perder los cambios. Una vez creado el proyecto, nap_project_desktop_app()
se limitará a llamar a add_subdirectory()
. El comando nap_desktop_app()
del /hello/CMakeLists.txt
sabe como manejar las particularidades entre plataformas. Por ejemplo, en caso de macOS creará un bundle en lugar de un ejecutable aislado.
No tenemos porque limitarnos a una sola aplicación. Nuestra solución admitirá numerosos targets. Por ejemplo añade esta línea al CMakeLists.txt
y vuelve a lanzar cmake -S . -B build
.
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) |
En caso de que la solución ya estuviese abierta, es posible que el IDE te avise que han habido cambios (Figura 4). Tras pulsar [Reload]
, verás que ha aparecido el nuevo proyecto (Figura 5).
3. Añadir archivos
Volviendo al proyecto napphello
, vemos que por defecto solo se crea un archivo de código fuente (napphello.c
) que contiene toda la aplicación. Es muy probable que quieras dividir el código entre diferentes archivos. Crea un par de nuevos archivos /hello/myfunc.c
y /hello/myfunc.h
desde el IDE o directamente desde el explorador. Ábrelos y añade estas líneas:
1 2 3 4 5 |
1 2 3 4 5 6 7 8 |
Abre /hello/napphello.c
y edita la función i_OnButton
.
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); } ... |
Vuelve a re-generar la solución con cmake -S . -B build
. El IDE, Visual Studio en este caso, nos vuelve a informar que han habido cambios en el proyecto napphello
. Simplemente presiona [Reload All]
como ya hicimos en el caso anterior.
Vuelve a compilar y ejecutar napphello
para ver los cambios que acabas de realizar. Puedes crear tantos archivos y subcarpetas dentro del directorio /hello
como necesites para organizar mejor tu código. Recuerda siempre ejecutar cmake -S . -B build
cada vez que añadas o elimines archivos del proyecto. El comando nap_desktop_app()
actualizará la solución "clonando" la estructura de directorios dentro del proyecto (napphello
en este caso).
Llegados a este punto te recomendamos que dediques algo de tiempo a investigar, compilar y probar los ejemplos de la carpeta demo
dentro del repositorio de NAppGUI.
4. Aplicaciones por línea de comandos
De forma similar a las aplicaciones de escritorio vistas anteriormente, es posible crear aplicaciones de consola. Añade esta nueva línea al CMakeLists.txt
de la solución.
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) |
Al regenerar la solución con cmake -S . -B build
, Visual Studio te volverá a alertar que debes recargar la solución. Se habrá creado un nuevo proyecto en ./utils/myutil
(Figura 6), pero esta vez si lo compilas y ejecutas no aparecerá ninguna ventana. Tan solo verás un mensaje en la consola de Visual Studio:
1 |
Hello world! |
Si abres myutil.c
encontrarás el código que ha generado la salida anterior:
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; } |
Que es la típica plantilla de un programa en C, a la que se le ha incluido el soporte de la librería core. A partir de aquí, ya podemos modificar el código y compilar. nap_command_app()
ya lo configuró todo por nosotros.
|
nap_project_command_app(appName path) |
appName
: El nombre de la aplicación.path
: Ruta relativa a.
donde se ubicará el proyecto (en este caso./utils/myutil
).
Ni que decir tiene que el comportamiento de nap_project_command_app()
es idéntico al de nap_project_desktop_app()
. No sobrescribirá los archivos del proyecto una vez creado e integrará todos los nuevos archivos que añadamos en un futuro.
5. Uso de librerías
Imaginemos que nuestras tres aplicaciones necesitaran compartir ciertas funcionalidades. Lo más inteligente sería encapsular estas funciones en una biblioteca y que las tres tuvieran acceso a ellas. Esto lo lograremos insertando una nueva línea en nuestro 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) |
Observa que el comando que hemos insertado nap_project_library()
precede a las aplicaciones. Esto se debe a que CMake necesita procesar las dependencias antes que los proyectos que las utilizan.
|
nap_project_library(libName path) |
libName
: Nombre de la librería.path
: Ruta relativa aCMakeLists.txt
donde se ubicará el proyecto (en este caso./common
).
Al igual que con los proyectos de aplicaciones, la primera vez que se ejecuta nap_project_library()
, se crean una serie de archivos predeterminados. Posteriormente se podrán editar, eliminar o añadir más como acabamos de ver en el caso de las aplicaciones:
common.def
: Archivo que define la macro_common_api
necesaria para la exportación de símbolos. Más información en Símbolos y visibilidad.common.hxx
: Aquí incluiremos las definiciones de tipos públicos, comoenum
,struct
. Por el momento,common
no contiene tipos públicos.common.h
: Archivo de cabecera. Aquí escribiremos las declaraciones de funciones generales de la librería. De forma predeterminada, CMake crea dos:common_start()
ycommon_finish()
, donde implementaríamos, si fuera necesario, el código de inicio y finalización de la librería.common.c
: Implementación de funciones generales.CMakeLists.txt
: Donde se utiliza el comandonap_library()
, análogo anap_desktop_app()
que manejará las particularidades de cada plataforma.
Abre common.h
y common.c
, agregando una nueva función:
Edita el comando nap_desktop_app()
en /hello/CMakeLists.txt
, e incluye la dependencia con common
:
1 |
nap_desktop_app(napphello "common" NRC_NONE) |
Ejecuta cmake -S -B build
nuevamente para que todos los cambios surtan efecto. Ahora ya es posible utilizar la nueva función common_add
dentro de 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); } |
Puedes crear tantas librerías como tu proyecto necesite. Lo único que debes tener en cuenta es incluir el nombre de la nueva dependencia en los comandos nap_desktop_app()
, nap_command_app()
o nap_library()
de cada target. En Crear nueva librería seguiremos profundizando en el uso de las librerías.
6. Estándar C/C++
Por lo general, los compiladores permiten comprobar que el código se ajuste a ciertos estándares de C/C++, emitiendo advertencias o errores cuando no sea así. En pro de la portabilidad, todos los proyectos generados por nap_desktop_app()
y nap_command_app()
establecen los estándares más antiguos (C90
y C++98
respectivamente). Es posible que quieras utilizar estándares más modernos en tus proyectos. Abre /hello/CMakeLists.txt
y añade estas dos líneas:
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}) |
El comando nap_target_c_standard()
ha establecido el estándar C11
para napphello
. Igual forma, nap_target_cxx_standard()
ha seleccionado C++14
.
- Estándar C:
90
,99
,11
,17
y23
. - Estándar C++:
98
,11
,14
,17
,20
,23
y26
.
Si CMake o el compilador no soportasen el estándar indicado, se establecerá el mayor permitido. Es responsabilidad del programador utilizar los compiladores apropiados al estándar elegido.
7. NAppCompilers.cmake
De igual forma que el módulo NAppProject.cmake
nos ayuda a crear y configurar nuestros propios proyectos, NAppCompilers.cmake
que hace lo propio con los compiladores. Para utilizarlo en tu proyecto, tan solo deberás añadir estas dos líneas a tu CMakeLists.txt
principal.
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) |
La función nap_config_compiler()
detecta el compilador entre todos los soportados por NAppGUI: MSVC
, GCC
, Clang
y AppleClang
, estableciendo las opciones típicas para cada configuración y plataforma. También añade soporte para las diferentes opciones de configuración de CMake que utiliza NAppGUI en sus propias librerías y aplicaciones de ejemplo. Más información en win_macos_linux.