한국어
오픈소스포럼
 이곳은 다양한 오픈소스 프로젝트를 소개하고 리뷰, 활용 방법을 공유합니다.

NAppGUI는 C 프로그래밍 언어(ANSI C90)를 사용하여 크로스 플랫폼 데스크톱(Windows, macOS 또는 Linux) 애플리케이션을 구축하기 위한 라이브러리다.

c_programming_stack.png
운영체제 네이티브 API 위에 경량의 레이어로 구축되어 외부 종속성 없이 작고 빠르고 이식 가능한 프로그램을 만들 수 있다. C++로도 가능하지만 필수는 아니며 C언어만 사용하여 완전한 프로그램을 작성할 수 있다. (잘 작성된 문서가 제공되서 맘에든다.)

애플리케이션의 국제화 및 현지화 지원도 쉽게 가능하다. 모든 텍스트에 UTF-8을 사용하므로 세계 모든 언어에 대한 번역을 추가하고 응용 프로그램을 다시 실행 할 필요 없이 동적으로 텍스트를 조정하는 실시간 인터페이스 구성기능을 제공한다.

static void i_OnLangPopUp(Ctrl *ctrl, Event *e)
{
    const EvButton *params = event_params(e, EvButton);
    static const char_t *LANGS[] = { "en_US", "es_ES", "pt_PT", "it_IT", "vi_VN", "ru_RU", "ja_JP" };
    gui_language(LANGS[params->index]);
}

NAppGUI는 MIT 라이선스에 따라 배포되며, 이는 상업 및 오픈 프로젝트 모두에서 무료로 사용할 수 있음을 의미한다.

Windows에서 빠르게 시작하기

시작하기 전에 다음 도구가 설치되어 있어야 한다.

  • Microsoft Visual Studio C++ build tools 또는 Visual Studio
  • CMake
  • Git

첫 번째 단계는 GitHub를 통해 SDK 사본을 얻는 것이다. x64 Native Tools Command Prompt for VS 2019를 실행하고 다음과 같이 할 수 있다.

git clone --depth 1 https://github.com/frang75/nappgui.git nappgui_sdk

템플릿 디렉토리 구성
inc : SDK 헤더 파일.
lib : Windows, macOS 및 Linux 시스템용의 미리 컴파일된 NAppGUI 라이브러리(x86, x64, arm).
prj : CMake 빌드 스크립트 또는 솔루션을 생성하는 데 사용할 파일.
src : 예제 소스코드 또는 향후에 자신의 프로젝트를 추가할 위치.

다음 단계는 CMake 구성을 실행하고 컴파일을 실행한다.

# 빌드 디렉토리 생성
mkdir build
cd build

# CMake 구성 실행. 이 명령은 예제와 향후 프로젝트를 컴파일하는 데 필요한 NAppGUI.sln 솔루션을 자동으로 생성한다.
cmake -G "Visual Studio 16 2019" ../nappgui_sdk/src

# 동일한 작업 디렉토리에서 msbuild를 실행.
msbuild NAppGUI.sln

컴파일되면 demo 및 howto 디렉토리에서 예제 애플리케이션을 실행할 수 있다.

run_demo_windows.png

NAppGUI 응용프로그램 기본 구조

NAppGUI 응용 프로그램은 다른 시스템에서 데스크톱 프로그램의 시작을 통합하는 다중 플랫폼 매크로인 osmain에서 시작한다. #include "osmain.h"에 정의되어 있으며 4개의 매개변수를 받는다. 생성자, 소멸자, 옵션 문자열, 응용 프로그램 개체의 유형이 그것이다. 이런 식으로 기본 골격은 다음과 같다.

#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)

지시문 #include "nappgui.h"는 단일 명령으로 NAppGUI의 많은 것들을 포함한다. 원하는 경우 필요한 헤더만 별도로 포함하도록 할 수 있다.

#include "gui.h"
#include "button.h"
#include "heap.h"
#include "label.h"
#include "layout.h"
#include "listener.h"
#include "panel.h"
#include "strings.h"
#include "v2d.h"
#include "vtext.h"
#include "window.h"

완전한 예제 프로그램

조금 더 실용적인 예제를 통해 어떻게 생겼는지 살펴볼 필요가 있다. 다음은 예제로 살펴볼 완전한 프로그램이다.
demo/hello/main.c

/* 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(ekWNSTD);
    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)

생성자

첫 번째 osmain 매개변수는 애플리케이션의 생성자다.
프로그램이 시작되자마자 특정 내부 자료 및 개체를 초기화해야 하고 모든 데스크톱 응용 프로그램에 고유한 메시지 루프를 시작해야 한다. 모든 것이 준비되면 생성자가 호출되어 응용 프로그램 개체를 만든다. 이 개체는 모든 유형이 될 수 있으며 Application 또는 이와 유사한 클래스에서 파생될 필요가 없다. 이 예제의 단순성을 감안할 때 응용 프로그램 개체에는 하나의 창만 표시된다.

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;
}

메인 패널

기본 창을 만들려면 창에 표시된 모든 인터페이스 컨트롤을 통합하는 컨테이너인 메인 패널이 필요하다. 패널 내부 공간은 레이아웃이라는 보이지 않는 그리드로 구성된다. 각 패널은 여러 레이아웃을 가질 수 있고 그 사이에서 재구성될 수 있지만 적어도 하나는 필요하다. 셀 내에서 다양한 인터페이스 컨트롤을 찾는다.

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;
}

소멸자

응용 프로그램이 종료되면 osmain은 소멸자(매크로의 두 번째 매개변수)를 호출하여 프로그램을 완전히 종료하기 위해 응용 프로그램 개체와 이에 종속된 모든 것을 해제한다. 모든 메모리를 적절하게 해제하지 못하면 심각한 코딩 오류로 간주되기 때문에 우리는 이것에 많은 중점을 둬야 한다.

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

Window 보이기

기본적으로 NAppGUI는 모든 창을 숨김 모드로 생성하므로 명시적으로 표시해야 한다. 제목 및 초기 위치를 설정하고 window_show로 시작한다.

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);
   ...
}

hello_world_without_format.png

레이아웃 형식

창의 모양을 개선하기 위해 디자인에 약간의 형식을 지정해 본다. 특히, 세 번째 행(텍스트 컨트롤)의 열 너비와 높이를 설정한다. 그런 다음 가장자리에 여백을 남기고 행 사이를 분리한다.

layout_hsize(layout, 0, 200);
layout_vsize(layout, 2, 100);
layout_margin(layout, 5);
layout_vmargin(layout, 0, 5);
layout_vmargin(layout, 1, 5);

hello_world_formatted.png

프로그램 닫기

기본적으로 메인 창을 닫을 때 프로그램이 종료되지 않는다. 이것은 열려 있는 창이 없어도 Dock에서 계속 실행되는 macOS 응용 프로그램의 일반적인 것이다. NAppGUI는 프로그램을 닫지 않는 것과 동일한 기준을 따르므로 osapp_finish 함수를 명시적으로 호출해야 한다. 이를 위해 listener매크로를 통해 버튼 이벤트를 캡처한다.

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));
}

버튼 이벤트

마지막으로 버튼의 클릭 이벤트를 캡처하고 누를 때마다 텍스트 상자에 메시지를 표시한다. 메시지 작성 및 표시를 담당하는 i_OnButton 핸들러를 구현하고 이전에 만든 Button 컨트롤에 연결한다.

static void i_OnButton(App *app, Event *e)
{
    String *msg = str_printf("Button click (%d)\n", app->clicks);
    text_insert(app->vtext, tc(msg));
    str_destroy(&msg);
    app->clicks += 1;
}
...
button_OnClick(button, listener(app, i_OnButton, App));

 

공식 웹사이트: https://nappgui.com
저장소: https://github.com/frang75/nappgui_src