JUCE — Кроссплатформенный C++ фреймворк для разработки приложений с пользовательским интерфейсом

в 13:23, , рубрики: Без рубрики

image

Приветствую хабросообщество!
Наверно каждый кто профессионально разрабатывает ПО или просто увлекается программированием, рано или поздно приходил к необходимости создавать пользовательский интерфейс для своей программы. И если не рассматривать нативные для платформ окружения и языки такие как C# для Windows или Objective-C для Mac OS X которые изначально содержат средства для визуализации интерфейса, то выбор оказывается не очень богатым, особенно если мы не горим желанием платить деньги за средства разработки GUI или желаем добиться кроссплатформенности.
В своем первом посте на Хабре я бы хотел рассказать о таком фремворке как JUCE. Поиск по Хабру выдал всего 2 статьи где данный фреймворк только упоминается, но ни какой детальной информации не приводится. Думаю что тем кто только начинает осваивать кроссплатформенные GUI приложения на C++ будет интересно узнать об альтернативе таким монстрам как Qt или таким старичкам как GTK+

Что такое JUCE

Официальный сайт разработчиков: juce.com.

JUCE (Jules' Utility Class Extensions) это всеохватывающая библиотека классов С++ для разработки кроссплатформенного программного обеспечения.
Он содержит практически все что вам может понадобиться для создания большинства приложений, особенно хорошо подходит для построения сложных GUI, обработки графики и аудио.

Википедия:

Juce — это открытый кроссплатформенный инструментарий разработки ПО (фреймворк) для языка C++, используемый для разработки GUI приложений и плагинов.

JUCE поддерживает следующие платформы и возможности:

  • Mac OS X Приложения и VST/AudioUnit/RTAS/NPAPI плагины компилируются при помощи Xcode. (на 10.7.5 & Xcode 4.6.3 все работает без проблем)
  • Windows Приложения и VST/AudioUnit/RTAS/NPAPI/ActiveX плагины собираются при помощи MS Visual Studio. Результаты полностью совместимы с Windows XP, Vista и Win7/8
  • Linux Приложения и плагины могут быть собраны для любого ядра версии 2.6 и старше.
  • iOS Нативные iPhone и iPad приложения собираются при помощи Xcode
  • Android Android приложения собираются при помощи Ant или Eclipse с использованием Android NDK.

JUCE разрабатывался силами одного человека и из набора классов для личного использования перерос в полноценный фреймворк.
OpenSource лицензия появилась в 2003 году, а с 2005 фреймворк обзавелся платной ее версией для закрытого ПО.

Большинство модулей фреймворка имеют открытую лицензию GPLv2, v3 и AGPLv3, модуль Core имеет лицензию ISC

Если у вас нет желания публиковать исходный код своей программы, то вы можете приобрести коммерческую лицензию по следующим расценкам:

  • Лицензия на один продукт — 399 Английских Фунтов
  • Лицензия на неограниченное колличество продуктов — 699 Фунтов
  • Апгрейд с первой на вторую — 349 Фунтов
  • Апгрейд старых версий — до полной 349 Фунтов до лицензии на один продукт 199 Фунтов

Состав фреймворка и начало работы

Сам JUCE достаточно легковесный, чуть более 28 Мб. Папка docs содержит подробную документацию по установке и подключению фреймворка в различных ОС и средах разработки.
Касательно установки стоит заметить что JUCEпредставляет собой не набор библиотек в привычном смысле или набор исходников из которых вы должны собрать библиотеки для статической или динамической линковки, а набор заголовочных файлов и файлов исходного кода, содержащие все предоставляемые фреймворком классы. Данный подход не только упрощает миграцию с одной платформы на другую, избавляя вас от необходимости включать в состав дистрибутивов библиотеки фреймворка или требовать их наличия на компьютере пользователя, но и решает проблему разрядности библиотек от которых зависит фреймворк. Так как стандартные библиотеки или стандартное окружение современных ОС которое используется в проектах JUCE (все они описываются в документации поставляемой в комплекте с фреймворком) как правило собраны с поддержкой как 32 так и 64 bit. Так что вам стоит следить за битность только тех библиотек которые вы дополнительно используете в своем проекте.
Все модули фреймворка находятся в папке modules. Весь исходный код хорошо структурирован и комментирован, так что проблем с пониманием возникнуть не делжно.
В папке extras можно найти примеры использования фреймворка. Среди прочего там же вы найдете две утилиты, которые помогут вам в разработке. Для этих утилит, да и для всех примеров, уже есть готовые проекты для различных платформ и IDE, все что вам остается это открыть проект и скомпилировать пример или утилиту.
Первая утилита это Introjucer, достаточно аскетичная программа для автоматической генерации проектов, написана с использованием самого JUCE и является ярким примером возможностей фреймворка. В комплекте присутствует мастер по созданию проектов, мастер по созданию шаблонов исходно кода и очень весомое дополнение — визуальный редактор GUI, который просто генерирует на основе ваших манипуляций код на С++ в соответствующий файл исходного кода (который до того был создан при помощи мастера шаблонов), редактор кода, мастер локализации и ряд других достаточно полезных инструментов.

Немного картинок для наглядности

Конфигурация проекта
JUCE — Кроссплатформенный C++ фреймворк для разработки приложений с пользовательским интерфейсом
Редактор кода
image
Редактор GUI
image
Добавление sourcecode файлов
image

Приятной изюменкой в Introjucer является наличие собственного файла проекта, настроенного под все IDE и платформы которые поддерживаются фреймворком. Для переноса кода от разработчика не потребуется ни чего, кроме наличи на целевой системе утилиты Introjucer. Каждый проект сформированный утилитой можно открыть в целевой IDE которую вы используете для своей ОС.
image

Вторая утилита это binarybuilder, которая позволяет вам перенести бинарные файлы непосредственно в код ваших исходных файлов. На пример картинки и логотипы превратятся в набор бинарных данных которые вы можете использовать в своем коде. Эта утилита так же является частью Introjucer и используется там в менеджере ресурсов при редактировании GUI. Но может быть собрана как отдельное консольное приложение.
image

Hello World

И куда же без примера кода…
Рассмотрим подробнее пример поставляемый с фреймворком:
../extras/example projects:
В проектах JUCE все заголовочные файлы в конечном итоге вкладываются в один файл "../JuceLibraryCode/JuceHeader.h".
Так что разработчику не приходится подключать весь ворох классов из фреймворка вручную в каждом исходном файле.
В рассматриваемом примере присутствует два модуля программы:

  • Main.cpp который отвечает не посредственно за само наше приложение — старт, вывод окна на экран, завершение и т.д.
  • MainComponent.cpp(.h) который отвечает за наполнение нашего окна — кнопки, фон, поля для ввода текста и т.д.

Стоит заметить что в JUCE все содержимое GUI является классом, наследуемым от класса JUCE Component.
Рассмотрим наш MainComponent.h:

#ifndef __JUCE_HEADER_9002020A4DD09B20__
#define __JUCE_HEADER_9002020A4DD09B20__

//Подключаем наш фреймворк
#include "../JuceLibraryCode/JuceHeader.h"

//И описываем наш класс
class MainComponent  : public Component, //Наследуем от основного класса фреймворка
                                             public ButtonListener //данный клас отвечает за прослушивание событий кнопок интерфейса

Далее требуется описать поведение класса, его конструкторы и деструкторы.
В секции public находится следующий код:

MainComponent (); 
~MainComponent();

    void paint (Graphics& g); //метод отвечающий за отрисовку в canvas
    void resized(); //метод вызываемый при изменении размера
    void buttonClicked (Button* buttonThatWasClicked); //метод вызываемый при нажатии на кнопку

В секции private находятся непосредственно наши элементы интерфейса:

    ScopedPointer<Label> helloWorldLabel;
    ScopedPointer<TextButton> quitButton;
    Path internalPath1;

Перейдем непосредственно к логике работы нашего GUI в MainComponent.cpp:

//Не забываем подключить заголовочный файл нашего класса
#include "MainComponent.h"

//Добовляем логику для конструктора
MainComponent::MainComponent ()
{
    addAndMakeVisible (helloWorldLabel = new Label (String::empty,
                                                    "Hello World!")); //Добавляем наш текстовый лэйбл, делаем его видимым и добавляем дополнительные параметры
    helloWorldLabel->setFont (Font (40.00f, Font::bold));
    helloWorldLabel->setJustificationType (Justification::centred);
    helloWorldLabel->setEditable (false, false, false);
    helloWorldLabel->setColour (Label::textColourId, Colours::black);
    helloWorldLabel->setColour (TextEditor::textColourId, Colours::black);
    helloWorldLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    addAndMakeVisible (quitButton = new TextButton (String::empty)); //То же самое делаем с нашей кнопкой
    quitButton->setButtonText ("Quit");
    quitButton->addListener (this);

    setSize (600, 300); //Задаем размер нашего содержимого

}

//Деструктор просто уничтожает содержимое нашего GUI 
MainComponent::~MainComponent()
{
  
    helloWorldLabel = nullptr;
    quitButton = nullptr;

}

//И описываем все методы нашего класса
void MainComponent::paint (Graphics& g)
{

    g.fillAll (Colour (0xffc1d0ff));

    g.setColour (Colours::white);
    g.fillPath (internalPath1);
    g.setColour (Colour (0xff6f6f6f));
    g.strokePath (internalPath1, PathStrokeType (5.200f));

}

void MainComponent::resized()
{
    helloWorldLabel->setBounds (152, 80, 296, 48);
    quitButton->setBounds (getWidth() - 176, getHeight() - 60, 120, 32);
    internalPath1.clear();
    internalPath1.startNewSubPath (136.0f, 80.0f);
    internalPath1.quadraticTo (176.0f, 24.0f, 328.0f, 32.0f);
    internalPath1.quadraticTo (472.0f, 40.0f, 472.0f, 104.0f);
    internalPath1.quadraticTo (472.0f, 192.0f, 232.0f, 176.0f);
    internalPath1.lineTo (184.0f, 216.0f);
    internalPath1.lineTo (200.0f, 168.0f);
    internalPath1.quadraticTo (96.0f, 136.0f, 136.0f, 80.0f);
    internalPath1.closeSubPath();


}

void MainComponent::buttonClicked (Button* buttonThatWasClicked)
{

    if (buttonThatWasClicked == quitButton)
    {

        JUCEApplication::quit();

    }

}

На этом наш класс готов к тому что бы поселиться в окне приложения. Перейдем к Main.cpp и посмотрим каким образом инициализируется и запускается наш пример.

//Поключаем наш фреймворк и класс контента
#include "../JuceLibraryCode/JuceHeader.h"
#include "MainComponent.h"

//Описываем окно нашего приложения
class HelloWorldWindow  : public DocumentWindow
{
public:

HelloWorldWindow(): DocumentWindow ("JUCE Hello World!",
                          Colours::lightgrey,
                          DocumentWindow::allButtons,
                          true)
{
        // Заполняем окно нашим содержимым
        setContentOwned (new MainComponent(), true);

        // Задаем положение по центру
        centreWithSize (getWidth(), getHeight());

        // И отображаем его
        setVisible (true);
    }

~HelloWorldWindow()
    {
        // Описываем деструктор если он нам нужен
    }

void closeButtonPressed()
    {
        
        // Описываем реакцию на нажатие кнопки закрытия
        JUCEApplication::quit();
    }
};

Окно мы описали, теперь очередь за самим приложением:

class JUCEHelloWorldApplication : public JUCEApplication //Наследуем от основного класса приложения JUCE
{
public:
    JUCEHelloWorldApplication() {}

    void initialise (const String& commandLine)
    {
        // Инициализируем приложение создавая наше окно
        helloWorldWindow = new HelloWorldWindow();
    }

    void shutdown()
    {
        
        // Описываем поведение при закрытии приложения
        helloWorldWindow = nullptr;
    }
    //Далее задаются методы для получения некоторой информации о приложении
    const String getApplicationName()
    {
        return "Hello World for JUCE";
    }

    const String getApplicationVersion()
    {
        //  ProjectInfo::versionString автоматически обновляется Jucer, и
        // описывается в JuceHeader.h который генерируется для нашего проекта
        return ProjectInfo::versionString;
    }

    bool moreThanOneInstanceAllowed() //Разрешаем запуск нескольких версий
    {
        return true;
    }

    void anotherInstanceStarted (const String& commandLine)
    {
    }

private:
    ScopedPointer<HelloWorldWindow> helloWorldWindow; //свойства класса нашего приложения, по сути все окна которые в нем содержаться

// Этот макрос создает точку входа main() нашего приложения.
START_JUCE_APPLICATION (JUCEHelloWorldApplication)

Все готово, если наш проект собрать и запустить то мы получаем вот такой результат:
image

Если вы желаете посмотреть на что еще способен JUCE рекомендую собрать и запустить приложение Demo из папки extras, в котором показаны основные возможности данного фреймворка, но не забывайте что их гораздо больше и все зависит от того как вы примените то что имеете.

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

Послесловие

Надеюсь кому то мой экспромт окажется полезным.
Мое личное мнение таково: данный фреймворк заслуживает внимания и того, что бы использоваться в малых и больших проектах.
С удовольствием приму любую критику и замечания, спасибо что обратили внимание на данный пост!

Автор: GunGraveKoga

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js