Nuklear+ — миниатюрный кроссплатформенный GUI

    Nuklear+ (читается как "Nuklear cross", значит "кроссплатформенный Nuklear") — это надстройка над GUI библиотекой Nuklear, которая позволяет абстрагироваться от драйвера вывода и взаимодействия с операционной системой. Нужно написать один простой код, а он потом уже сможет скомпилироваться под все поддерживаемые платформы.


    Я уже писал на хабре статью "Nuklear — идеальный GUI для микро-проектов?". Тогда задача была простой — сделать маленькую кроссплатформенную утилиту с GUI, которая будет примерно одинаково выглядеть в Windows и Linux. Но с тех самых пор меня не отпускал вопрос, а можно ли на Nuklear сделать что-то более-менее сложное? Можно ли целиком на нём сделать какой-нибудь реальный проект, которым будут пользоваться?


    Веб-демо Wordlase


    Именно поэтому следующую свою игру, Wordlase, я делал на чистом Nuklear. И без всякого там OpenGL. Даже фоновые картинки у меня имеют тип nk_image. В конечном итоге это дало возможность выбора драйвера отрисовки, вплоть до чистого X11 или GDI+.


    Ещё в прошлой своей статье я заложил основы Nuklear+ — библиотеки, призванной спрятать всю "грязь" от программиста и дать ему сфокусироваться на создании интерфейса. Библиотека умеет загружать шрифты, картинки, создавать окно операционной системы и контекст отрисовки.


    Полный пример кода есть в Readme на GitHub. Там можно увидеть, что код получается довольно простой. Также я перенёс на Nuklear+ свои проекты dxBin2h и nuklear-webdemo. И сделать это было очень просто — вся инициализация заменяется на один вызов nkc_init, события обрабатываются nkc_poll_events, отрисовка функцией nkc_render, а в качестве деструктора вызывается nkc_shutdown.


    Демо Nuklear


    Но вернёмся к Wordlase, на примере которой и построена данная публикация. С недавних пор у игры есть веб-демо. Я не писал какого-то специфичного веб-кода для игры — это чистое С89 приложение, скомпилированное с помощью Emscripten. И если полностью следовать примеру из Readme Nuklear+ (а именно, использовать nkc_set_main_loop), то веб-версия приложения будет получена абсолютно на халяву, без особых лишних затрат.


    Бэкэнд и фронтэнд


    Самой интересной частью Nuklear+ являются поддерживаемые фронтэнды и бэкэнды. В данном случае под фронтэндом понимается часть, ответственная за взаимодействие с ОС и отрисовку окна. Т.е. непосредственно то, что видит пользователь. Реализации лежат в папке nkc_frontend. Сейчас поддерживаются: SDL, GLFW, X11, GDI+. Они не равносильны. Например, GDI+ использует WinAPI даже для рендера шрифтов и загрузки изображений, т.е. получить ровно такую же картинку в других ОС будет проблематично. Реализация так же не везде одинакова. Например, реализация Х11 пока не умеет изменять разрешение экрана в полноэкранном режиме (буду рад видеть Pull Request)


    Выбрать фронтэнд для своего приложения просто — нужно установить переменную препроцессора NKCD=NKC_x, где x это одно из: SDL, GLFW, XLIB, GDIP. Например: gcc -DNKCD=NKC_GLFW main.c


    Бэкэнд в данном случае выполняет непосредственно отрисовку. Реализация в папке nuklear_drivers. Отрисовка средствами любой версии OpenGL выдаёт примерно одинаковую картинку на всех ОС и фронтэндах. Ведь для загрузки изображений там всегда используется stb_image, а шрифт рендерится стандартными средствами Nuklear (тоже основано на stb). В то же время чистый Х11 драйвер даже не умеет загружать шрифты. Так что не забывайте тестировать своё приложение для выбранной пары бэкэнд+фронтэнд.


    Например: Wordlase, GLFW3, OpenGL 2, Windows
    Wordlase, GLFW3, OpenGL 2, Windows


    Или: Wordlase, SDL2, OpenGL ES, Linux
    Wordlase, SDL2, OpenGL ES, Linux


    В качестве бэкэнда по умолчанию выбран OpenGL2, если доступен. Можно задать NKC_USE_OPENGL=3 для OpenGL 3, и NKC_USE_OPENGL=NGL_ES2 для OpenGL ES 2.0. Для использования чистого Х11 отрисовщика константу NKC_USE_OPENGL указывать не надо. Также OpenGL опции не влияют на GDI+ — там отрисовка всегда идёт своими средствами.


    Вот скриншот с GDI+: Wordlase, GDI+, без OpenGL, Windows


    Wordlase, GDI+, без OpenGL, Windows


    Этот бэкэнд полноценно поддерживает полупрозрачные изображения, картинка близка к оригиналу. Разница в шрифте: хинтинг, сглаживание, да даже размер (также буду рад Pull Request'у для автоматической подстройки размера GDI+ шрифта под размер stb_ttf).


    И самый ужасный случай — чистый Х11 отрисовщик, который до моего pull request даже не умел загружать картинки. Wordlase, X11, без OpenGL, Linux:


    Wordlase, X11, без OpenGL, Linux


    Вот здесь уже довольно много отличий: логотип, солнечные лучи, более острый край девушки, шрифт. Почему? Фон в игре на лету собирается из нескольких полупрозрачных PNG. Но чистый Х11 поддерживает только битовую прозрачность, прямо как GIF. Также отрисовщик Х11 очень медленно работает на больших изображениях с прозрачностью. А если в движке отключить прозрачность, то картинка становится ещё хуже. Wordlase, X11, без OpenGL, без прозрачности:


    Wordlase, X11, без OpenGL, без прозрачности


    Так зачем вообще нужны отрисовщики GDI+ и Х11, если они так уродливы? Потому, что они плохи только для больших изображений с прозрачностью. А если делать маленькую утилиту, где картинки используются только как иконки пользовательского интерфейса, то эти отрисовщики становятся вовсе неплохим вариантом, т.к. имеют минимальное количество зависимостей. Также я пользовался чистым Х11 отрисовщиком на слабых системах, где OpenGL только программный. В таком случае Х11 работает быстрее OpenGL. Подсказка: если вместо кучи полупрозрачных PNG использовать один большой JPEG, то Х11 будет работать быстро и корректно.


    Пример хорошего использования чистого Х11 бэкэнда — главное игровое окно Wordlase. Больших картинок там почти нет, зато есть несколько интерфейсных иконок, которые вполне корректно отображаются:


    Wordlase, X11, gameplay


    Отлично, отрисовщик выбран, окно ОС создаётся. Теперь самое время заняться GUI!


    Фишки Nuklear


    Самым первым в Wordlase показывается экран выбора языка:


    Wordlase, экран выбора языка


    Здесь видны сразу 2 интересных техники: несколько картинок на фоне окна и центрирование виджетов.


    Поместить картинку на фон окна достаточно просто:


    nk_layout_space_push(ctx, nk_rect(x, y, width, height));
    nk_image(ctx, img);

    x и y — позиция на экране, width и height — размеры изображения.


    Центрирование является более сложной задачей, т.к. не поддерживается Nuklear напрямую. Нужно вычислять положение самостоятельно:


    if ( nk_begin(ctx, WIN_TITLE, 
            nk_rect(0, 0, winWidth, winHeight), NK_WINDOW_NO_SCROLLBAR)
    ) {
        int i;
        /* 0.2 are a space skip on button's left and right, 0.6 - button */
        static const float ratio[] = {0.2f, 0.6f, 0.2f};  /* 0.2+0.6+0.2=1 */
    
        /* Just make vertical skip with calculated height of static row  */
        nk_layout_row_static(ctx, 
            (winHeight - (BUTTON_HEIGHT+VSPACE_SKIP)*langCount )/2, 15, 1
        );
    
        nk_layout_row(ctx, NK_DYNAMIC, BUTTON_HEIGHT, 3, ratio);
        for(i=0; i<langCount; i++){
            nk_spacing(ctx, 1); /* skip 0.2 left */
            if( nk_button_image_label(ctx, image, caption, NK_TEXT_CENTERED) 
            ){
                loadLang(nkcHandle, ctx, i);
            }
            nk_spacing(ctx, 1); /* skip 0.2 right */
        }
    }
    nk_end(ctx);

    Следующая прикольная штучка — выбор темы оформления в настройках:


    Wordlase, выбор темы оформления


    Реализуется тоже просто:


    if (nk_combo_begin_color(ctx, themeColors[s.curTheme], 
        nk_vec2(nk_widget_width(ctx), (LINE_HEIGHT+5)*WTHEME_COUNT) ) 
    ){
        int i;
        nk_layout_row_dynamic(ctx, LINE_HEIGHT, 1);
        for(i=0; i<WTHEME_COUNT; i++)
            if( nk_button_color(ctx, themeColors[i]) ){
                nk_combo_close(ctx);
                changeGUItheme(nkcHandle, s.curTheme);
            }
        nk_combo_end(ctx);
    }

    Здесь главное понимать, что всплывающее поле combo — это такое же окно, как и главное. И располагать там можно что угодно.


    Самым сложно выглядящим окном является основное игровое окно:


    Wordlase, основное игровое окно


    На самом деле, тут тоже нет ничего сложного. На экране всего 4 ряда:


    1. Верхняя линия с выбором уровня (виджет nk_property_int)
    2. Список слов (nk_group_scrolled)
    3. Кнопки текущего слова
    4. Строка с подсказкой

    Единственный непонятный момент здесь — задание точных размеров элементам. Выполняется это с помощью соотношения ряда:


    float ratio[] = {
        (float)BUTTON_HEIGHT/winWidth, /* square button */
        (float)BUTTON_HEIGHT/winWidth,  /* square button */
        (float)topWordSpace/winWidth, 
        (float)WORD_WIDTH/winWidth
    };
    nk_layout_row(ctx, NK_DYNAMIC, BUTTON_HEIGHT, 4, ratio);

    BUTTON_HEIGHT и WORD_WIDTH — константы, измеряются в пикселях; topWordSpace вычисляется как ширина экрана минус ширины всех остальных элементов.


    И последнее сложно выглядящее окно — статистика:


    Wordlase, статистика


    Расположение элементов регулируется с помощью группировки. Ведь всегда можно сказать Nuklear: "в этом ряду будет 2 виджета". Но группа тоже является виджетом. Т.е. можно просто создать группу с помощью nk_group_begin и nk_group_end, а дальше позиционироваться внутри неё как внутри обычного окна (nk_layout_row и пр.).


    Заключение


    Nuklear уже готов даже для коммерческих игр и приложений. А Nuklear+ может сделать их создание более приятным.


    Полезные ссылки


    • +16
    • 10,1k
    • 5
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 5
    • +1
      Как можно получить доступ к контролам извне? Отрисовать интерфейс мало, его еще нужно как то тестировать. Если для браузеров есть web driver, для WPF есть Automation UI. Как программно взаимодействовать с интерфейсом написанном при помощи данной библиотеки?
      • +1

        Я с такой задачей не сталкивался. Nuklear — это порядка 18к строк кода. Не думаю, что там внутри есть что-то подобное. Да и набор контроллов ограничен. Т.е. если планируется сложный навороченный интерфейс, то лучше взять что-нибудь другое.


        Nuklear уже готов даже для коммерческих приложений

        Здесь я имел ввиду, что библиотека стабильна и свой функционал предоставляет хорошо. Т.е. если нужно сделать какую-нибудь микро-утилиту с 2 кнопками, то не обязательно для этого с собой тащить Qt. Но если делать сложный UI на Nuklear — это уже скорее ближе к извращению. Для каждой задачи свой инструмент.

        • +1
          Сам nuklear не занимается эвентами от системы, их ему надо передавать самостоятельно (с чем и помогает nuklear+). Вот как эвенты передают в нормальном режиме — точно так же вы можете их передавать от тестовых сценариев. Так же состояние доступно через nk_context, можно изменять состояние виждетов не через эвенты.
        • 0

          На телефонах я так понял не работает?! При открытии демо просто выбор языка и дальше не чего.

          • 0

            Да, в мобильных браузерах в веб-версии почему-то происходит Select, а не Click. С удовольствием приму Pull Request, исправляющий это поведение. :-)
            Нативную Andorid-версию я собирать пока не пробовал. Но скорее всего скомпилируется без проблем (т.к. Open GL ES 2.0 уже есть), и работать тоже скорее всего будет нормально (т.к. люди вроде бы уже пробовали, всё у них нормально).

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.