Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Clutter: руководство для начинающих разработчиков

Оригинал: Clutter: a beginner's tutorial
Автор: TuxRadar
Дата публикации: 27 ноября 2009 г.
Перевод: A.Панин
Дата перевода: 30 октября 2015 г.

Если вы не можете программировать, вы можете найти квалифицированного разработчика на сайте Toptal.

В рамках проекта Moblin развивалось большое количество отличных технологий, но Clutter, по нашему мнению, является лучшей из них. Почему мы считаем так? Если вас не убеждает в нашей правоте наличие использующего ускорение OpenGL объектно-ориентированного и интегрированного с тулкитом GTK API, возможно, вас заинтересует мощный встроенный фреймворк для создания анимаций, выполнения простых манипуляций с текстурами и чрезвычайно быстрой работы с объектами.

Единственной проблемой библиотеки Clutter является недостаточный объем документации - большое количество разработчиков желает научиться работать с данной технологией, но сейчас достаточно сложно найти какие-либо руководства в сети. Нам неприятно наблюдать за тем, как отличные свободные проекты не собирают пользовательской базы, которую они по праву заслуживают, поэтому мы потратили несколько часов на создание простого руководства для людей, которые хотят научиться работать с Clutter настолько быстро, насколько это возможно.

Компания Intel оказала спонсорскую помощь ресурсу TuxRadar, которая оказалась достаточно своевременной, поэтому вы можете наблюдать рекламу проекта Moblin рядом с некоторыми из наших статей. Подписчики журнала LinuxFormat, которые пользуются сервисом для подписчиков уже наверняка знают о том, что основной темой следующего номера журнала будут мобильные системы на основе Linux, а начиная с текущего номера мы начнем публиковать руководства, посвященные использованию языка программирования Python для разработки приложений для Moblin.

Мы планировали написание данных статей ранее, но после получения спонсорской помощи от компании Intel, мы решили, что следует начать работу над специальным выпуском журнала, который будет интересен людям, желающим поработать с технологиями проекта Moblin, не дожидаясь следующих выпусков.

Как известно, одним из действительно интересных компонентов проекта Moblin является Clutter, библиотека от разработчиков компании Intel, которая интенсивно используется для создания графических интерфейсов таких систем, как Moblin и Ubuntu Netbook Remix (а также GNOME 3 - примечание переводчика). Интерфейс Clutter прост в изучении и использовании, причем с ним достаточно приятно работать. Если у вас не нашлось времени для изучения данного интерфейса ранее, расслабьтесь: мы собираемся продемонстрировать вам простой метод разработки приложений на основе библиотеки Clutter. А если вы захотите узнать больше, вы всегда можете нажать на рекламу проекта Moblin - на сайте проекта достаточно материала, который также может оказаться полезным для вас в том случае, если вы будете точно знать, что именно следует искать.

Пожалуйста помните о том, что данная статья является всего лишь первым материалом, который был подготовлен нами для решения проблемы отсутствия документации проекта; сейчас мы работаем над серией более подробных руководств по работе с библиотекой Clutter с использованием языка программирования Python, но для ее завершения нам потребуется как минимум пара месяцев. Пока же мы рекомендуем ознакомиться с данным скромным материалом и весело провести время, разрабатывая приложения на основе Clutter!

Первые шаги

Вместо того, чтобы ходить вокруг да около и обсуждать теоретическую информацию, давайте приступим к углубленному изучению библиотеки Clutter. Во-первых, следует установить пакеты программного обеспечения, необходимые для сборки приложений на основе Clutter; если вы используете дистрибутив Debian/Ubuntu, вам понадобятся пакеты libclitter-1.0.0 и libclutter-1.0-dev, а также, разумеется, компилятор GCC.

Окно Clutter называется "сценой" ("stage"), причем в мы можем добавлять объекты - "акторы" ("actors") на эту сцену. Если актор находится на сцене, Clutter будет осуществлять его отрисовку; мы сможем свободно перемещать его по сцене, вращать или масштабировать его, причем Clutter осуществит все необходимые преобразования за нас.

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

#include <clutter/clutter.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
        ClutterInitError state;
        
        state = clutter_init(&argc, &argv);
        
        if (state != CLUTTER_INIT_SUCCESS) return EXIT_FAILURE;
        
        ClutterColor stage_color = { 0, 0, 0, 255 };
        
        ClutterActor *stage = clutter_stage_new();
        clutter_actor_set_size(stage, 512, 512);
        clutter_actor_set_background_color(CLUTTER_ACTOR(stage), &stage_color);
        clutter_actor_show(stage);
        
        clutter_main();
        
        return EXIT_SUCCESS;
}

А теперь выполните следующую команду с помощью эмулятора терминала:

gcc example.c -o clutterapp `pkg-config clutter-1.0 --cflags --libs`

При удачном стечении обстоятельств в процессе компиляции не будет выведено сообщений об ошибках и вы сможете выполнить команду "./clutterapp" для запуска вашей программы. Если вы используете устаревший дистрибутив (например, Ubuntu 9.04) и обнаружили, что пакетов программного обеспечения с компонентами библиотеки Clutter версии 1.0 нет в репозитории вашего дистрибутива, вам придется использовать Clutter версии 0.9, но при этом стоит помнить о том, что данная версия библиотеки Clutter не на 100% совместима с версией 1.0. В том случае, если вы не захотите обновлять систему и попытаетесь продолжить работу, не используя функции Clutter версии 1.0, вам придется модифицировать некоторые вызовы функций для приведения их в соответствие со спецификацией, а также модифицировать вызов pkg-config, приведя его в соответствие с вашей версией библиотеки, т.е., в Ubuntu 9.04 следует использовать параметр clutter-0.9.

После того, как вы запустите приложение, вы обнаружите, что оно не является особо интересным: на экране появится черное окно с заголовком "./clutterapp" и больше ничего. Приложение не производит какого-либо впечатления, но его важность сложно переоценить: оно является доказательством того, что библиотека Clutter корректно установлена в вашей системе и готова к работе.

Первое приложение на основе Clutter - да, это всего лишь черное окно, но это также и доказательство корректной настройки вашего окружения разработки

Первое приложение на основе Clutter - да, это всего лишь черное окно, но это также и доказательство корректной настройки вашего окружения разработки

Давайте пройдемся по коду приложения и рассмотрим некоторые функции и типы данных...

  • clutter_init() инициализирует Clutter, после чего вы можете использовать все функции данной библиотеки. Мы передаем стандартные переменные argc и argvв качестве аргументов данной функции, так как Clutter использует некоторые параметры командной строки. Мы рассмотрим эти параметры позднее; на данный момент вам нужно знать лишь о том, что в случае передачи поддерживаемых библиотекой Clutter параметров, Clutter автоматически изменит значения переменных argc и argv таким образом, что данные параметры будут удалены и ваше приложение никогда не увидит их.

  • ClutterColor является простой структурой, хранящей значения красной, зеленой и синей составляющих цвета, а также значение прозрачности. Вы можете использовать значения из диапазона от 0 до 255 или от 0x00 до 0xff в том случае, если вам проще работать с шестнадцатеричными значениями. Обратите внимание на то, что слово "Color" ("Цвет") написано в американском стиле, что забавно, так как в рамках API Clutter также используется слово "Behaviour" ("Поведение"), написанное в английском стиле. Такое несоответствие может действовать на мозг.

  • ClutterActor является наиболее важным типом данных библиотеки Clutter, который мы используем в данном примере при получении стандартной сцены, создаваемой средствами Clutter в момент вызова функции clutter_init(). Технически сцена Clutter представлена отдельным типом данных, но на уровне библиотеки этот тип данных является всего лишь специализированным типом ClutterActor, поэтому мы можем осуществлять взаимные преобразования этих типов в случае необходимости.

  • clutter_actor_set_size() устанавливает размер актора. Очень удобная и простая в использовании функция. Вы должны передать указатель на актор (в данном случае, нашу сцену) в качестве первого параметра этой функции, а также размеры по осям X и Y в качестве второго и третьего параметров.

  • clutter_actor_set_background_color() устанавливает цвет сцены. Это очевидно.

  • clutter_actor_show() показывает актор; это очень важно, ведь без вызова данной функции ваше окно (в данном примере) или ваш актор не появится на экране.

  • clutter_main() является функцией для запуска главного цикла обработки событий библиотеки Clutter - при вызове данной функции контроль над приложением передается библиотеке Clutter, которая будет вызывать функции обратного вызова при наступлении интересующих нас событий. Но, как вы видите, в данном примере мы никак не обозначаем круг интересующих нас событий и это означает, что Clutter отобразит сцену и не будет делать ничего до того момента, пока вы не закроете окно.

Это и есть простейшая программа на основе библиотеки Clutter - вы можете использовать ее исходный код в качестве примера, добавляя фрагменты кода по мере работы над новыми приложениями, и вскоре вы оцените простоту и выигрыш во времени при создании новых приложений. Фактически, мы займемся именно этим: добавим актор на сцену.

Clutter позволяет выбирать типы акторов, которые будут использоваться приложением, но на данный момент нас будет интересовать актор в форме прямоугольника. Как и в случае со сценой, мы можем использовать для него переменную типа ClutterActor, поэтому давайте усовершенствуем наше приложение - разместите следующий код перед строкой с вызовом функции clutter_actor_show() из предыдущего примера:

ClutterColor actor_color = { 0, 255, 0, 128 };
ClutterActor *rect = clutter_actor_new();
clutter_actor_set_background_color(CLUTTER_ACTOR(rect), &actor_color);
clutter_actor_set_size(rect, 100, 100);
clutter_actor_set_position(rect, 100, 100);
clutter_actor_add_child(CLUTTER_ACTOR(stage), rect);
clutter_actor_show(rect);

Данный код позволяет создать новый прямоугольник зеленого цвета (с 50% прозрачностью), устанавливает его размер и позицию, после чего добавляет его на сцену. Обратите внимание на то, что в данном примере используется макрос CLUTTER_ACTOR(); данный макрос понадобится вам для преобразования актора сцены к типу ClutterActor.

После создания прямоугольника мы добавляем его на сцену и показываем его для того, чтобы он был отрисован средствами библиотеки Clutter.

Выполните приложение на основе измененного исходного кода и вы обнаружите, что наше окно стало немного более интересным: теперь в нем находится прямоугольник! Да, мы знаем, что эти примеры достаточно скучны, но расслабьтесь - работая над нами, мы постепенно двигаемся к более интересным примерам...

Теперь окно Clutter содержит актор типа ClutterActor и является немного более интересным

Теперь окно Clutter содержит актор типа ClutterActor и является немного более интересным

Прямоугольники по запросу

Попытаемся немного доработать пример с целью упрощения создания и позиционирования акторов. Для этого придется добавить следующую функцию в ваш код:

ClutterActor *create_rect(ClutterColor col) {
        ClutterActor *rect = clutter_actor_new();
        clutter_actor_set_background_color(CLUTTER_ACTOR(rect), &col);
        clutter_actor_set_size(rect, 256, 128);
        clutter_actor_set_position(rect, 128, 128);
        clutter_actor_add_child(CLUTTER_ACTOR(stage), rect);
        clutter_actor_show(rect);

        return rect;
}

Данная функция позволяет создать прямоугольник заданного цвета и переместить его в заданную точку экрана. Для того, чтобы задействовать данную функцию, вам придется переместить объявление переменной "сцены" куда-либо, откуда к ней можно будет обращаться из любой строки кода программы, а также объявить переменную актора-прямоугольника. Исходя из этого, разместите две следующие строки срезу же после блока директив #include в начале вашего файла исходного кода:

ClutterActor *stage;
ClutterActor *rect1 = NULL;

Важно: Мы не используем заголовочный файл для объявления прототипов всех наших функций. Исходя из этого, вам придется убедиться в том, что вы размещаете реализации ваших функций в файле исходного кода в корректном порядке: если функция A вызывает функцию B, реализация функции B должна находиться в файле исходного кода перед реализацией функции A. Ввиду того, что функция create_rect() будет использоваться в рамках функции main(), ее реализация должна быть размещена перед реализацией функции main(). Позднее мы будем создавать и другие функции и вам также придется размещать их в файле исходного кода перед функциями, которые их вызывают.

Теперь модифицируйте код вашей функции main() следующим образом:

int main(int argc, char *argv[]) {
        ClutterInitError state;
	
        state = clutter_init(&argc, &argv);
        
        if (state != CLUTTER_INIT_SUCCESS) return EXIT_FAILURE;

        ClutterColor stage_color = { 0, 0, 0, 255 };
        
        // используется глобальная переменная сцены вместо локальной
        stage = clutter_stage_new();
        clutter_actor_set_size(stage, 512, 512);
        clutter_actor_set_background_color(CLUTTER_ACTOR(stage), &stage_color);
        
        // создание актора-прямоугольника с помощью вызова новой функции
        ClutterColor red = { 255, 0, 0, 128 };
        rect1 = create_rect(red);

        clutter_actor_show(stage);

        clutter_main();

        return EXIT_SUCCESS;
}

Программа практически не изменилась. Фактически, в том случае, если вы скомпилируете измененный исходный код и выполните полученное приложение, вы увидите то же окно. Но ввиду того, что у нас появилась возможность создания прямоугольников по мере необходимости, мы можем создать еще несколько прямоугольников отличных цветов и анимировать их. Анимировать? Ах, да, мы еще не обсуждали этот вопрос, но не беспокойтесь, это достаточно просто.

В рамках библиотеки Clutter для анимации акторов используются шкалы времени (timelines), причем на каждом "шаге" анимации вы будете получать уведомления, которые позволяют изменить состояние вашей сцены по вашему желанию.

Давайте начнем с модификации нашего кода для создания и отрисовки нескольких прямоугольников. На данный момент мы не будем реализовывать анимации; давайте просто добьемся корректной работы механизма создания прямоугольников. Модифицируйте ваш блок объявления глобальных переменных в начале файла исходного кода следующим образом:

#include <clutter/clutter.h>
#include <stdlib.h>

ClutterActor *stage;
ClutterActor *rect1 = NULL;
ClutterActor *rect2 = NULL;
ClutterActor *rect3 = NULL;
ClutterActor *rect4 = NULL;
ClutterActor *rect5 = NULL;
ClutterActor *rect6 = NULL;

Очевидно, что мы будем работать со сценой, как и в предыдущих примерах, а также с шестью прямоугольниками. Мы можем создать прямоугольники разных цветов с помощью функции create_rect(), модифицировав функцию main() следующим образом:

int main(int argc, char *argv[]) {
        ClutterInitError state;
	
        state = clutter_init(&argc, &argv);
        
        if (state != CLUTTER_INIT_SUCCESS) return EXIT_FAILURE;

        ClutterColor stage_color = { 0, 0, 0, 255 };
        
        stage = clutter_stage_new();
        clutter_actor_set_size(stage, 512, 512);
        clutter_actor_set_background_color(CLUTTER_ACTOR(stage), &stage_color);
        
        ClutterColor red = { 255, 0, 0, 128 };
        ClutterColor green = { 0, 255, 0, 128 };
        ClutterColor blue = { 0, 0, 255, 128 };
        ClutterColor yellow = { 255, 255, 0, 128 };
        ClutterColor cyan = { 0, 255, 255, 128 };
        ClutterColor purple = { 255, 0, 255, 128 };

        rect1 = create_rect(red);
        rect2 = create_rect(green);
        rect3 = create_rect(blue);
        rect4 = create_rect(yellow);
        rect5 = create_rect(cyan);
        rect6 = create_rect(purple);
        
        clutter_actor_show(stage);
        
        clutter_main();
        
        return EXIT_SUCCESS;
}

Если вы скомпилируете этот код, вы не увидите шести прямоугольников - будет отображен лишь один прямоугольник. Это произойдет потому, что все прямоугольники будут размещены в одной точке и из-за этого будут полностью перекрывать друг друга.

А теперь перейдем к более интересному материалу: давайте заставим эти прямоугольники вращаться. В первую очередь следует объявить переменную, которая будет хранить значение угла поворота - добавьте данную строку в блок объявления глобальных переменных.

gdouble rotation = 0;

А теперь нужно создать и активировать нашу шкалу времени. Для создания шкалы времени следует использовать функцию clutter_timeline_new() и сообщить библиотеке Clutter о ее продолжительности. Значение продолжительности шкалы времени должно задаваться в миллисекундах, следовательно, значение 1000 означает "одна секунда". После создания шкалы времени мы должны будем создать обработчик сигнала "new-frame" в форме функции, которая будет вызываться при необходимости обновления анимации. Наконец, мы должны будем создать цикл шкалы времени и активировать его.

Разместите эти строки в коде вашей функции main() сразу же после вызова create_rect() для объекта прямоугольника rect6:

ClutterTimeline *timeline = clutter_timeline_new(60);
g_signal_connect(timeline, "new-frame", G_CALLBACK(on_timeline_new_frame), NULL);
clutter_timeline_set_repeat_count(timeline, -1); 
clutter_timeline_start(timeline);

Функция on_timeline_new_frame вызывается тогда, когда Clutter передает нам управление над сценой для обновления ее состояния. В данной функции используются очень специфические параметры, но это нормально - вы можете объявить их вручную в первый раз, после чего копировать и вставлять полученное объявление по мере необходимости.

Наша функция будет выполнять простую последовательность операций: сначала немного увеличивать значение переменной "rotation", после чего вызывать функцию clutter_actor_set_rotation_angle() для изменения углов наклона прямоугольников на разные значения. Так как мы используем глобальные переменные для хранения указателей на каждый из объектов акторов-прямоугольников, доступ к соответствующим объектам может быть получен без каких-либо сложностей, поэтому вам следует просто добавить реализацию данной функции в файл исходного кода приложения:

void on_timeline_new_frame(ClutterTimeline *timeline, gint frame_num, gpointer data) {
        rotation += 0.3;

        clutter_actor_set_rotation_angle(rect1, CLUTTER_Z_AXIS, rotation * 5);
        clutter_actor_set_rotation_angle(rect2, CLUTTER_Z_AXIS, rotation * 4);
        clutter_actor_set_rotation_angle(rect3, CLUTTER_Z_AXIS, rotation * 3);
        clutter_actor_set_rotation_angle(rect4, CLUTTER_Z_AXIS, rotation * 2);
        clutter_actor_set_rotation_angle(rect5, CLUTTER_Z_AXIS, rotation);
        clutter_actor_set_rotation_angle(rect6, CLUTTER_Z_AXIS, rotation * 0.5);
}

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

  1. Библиотека Clutter реализует отличный механизм автоматического освобождения памяти, зарезервированной для созданных с помощью нее объектов, но обычно данный механизм работает лишь с объектами, которые относятся к текущей сцене. Наш объект шкалы времени не связан с объектом сцены и это означает, что вам придется освобождать зарезервированную для него память вручную. Для этого следует добавить следующую строку сразу же после строки вызова функции clutter_main() в коде функции main():

    g_object_unref(timeline);
    
  2. Вращение акторов относительно верхнего левого угла используется достаточно редко. Более удачным вариантом является вращение актора относительно его центра и мы можем реализовать его, изменив "точку вращения" ("pivot point") актора Clutter. Модифицируйте вашу функцию create_rect() таким образом, чтобы она содержала следующую строку:

    clutter_actor_set_pivot_point(rect, 0.5f, 0.5f);
    

Вращающиеся прямоугольники с прозрачностью? Кажется, мы пришли к чему-то...

Вращающиеся прямоугольники с прозрачностью? Кажется, мы пришли к чему-то...

Масштабирование и выбор акторов

После того, как вы увидели, что средствами библиотеки Clutter можно достаточно просто организовать вращение акторов, давайте продвинемся немного дальше и добавим в приложение функцию масштабирования акторов-прямоугольников. Библиотека Clutter реализует очень продуманную систему, позволяющую пересчитывать значения параметров сцены через заданные промежутки времени, но, откровенно говоря, вопросы пересчета параметров сцены выходят за пределы руководства для начинающих разработчиков. Ввиду этого ваша задача будет значительно упрощена, так как мы будем использовать простой заголовочный файл языка программирования C, который позволит реализовать некоторые простые эффекты без приложения каких-либо усилий; загрузите данный файл в вашу рабочую директорию, измените его имя на mathhacks.h и добавьте соответствующую директиву #include в файл исходного кода приложения.

Благодаря данному заголовочному файлу мы можем осуществлять интерполяцию между двумя значениями с затуханием с обоих сторон, т.е., мы можем реализовать механизм плавного увеличения и уменьшения размеров прямоугольников, принимающий значение с плавающей точкой из диапазона от 0 до 1. Это значение должно где-либо храниться, поэтому вы должны добавить следующую строку после строки "gdouble rotation":

gdouble scale = 0;

После объявления переменной мы можем добавить код для масштабирования акторов в ранее реализованную функцию on_timeline_new_frame(); разместите следующие строки в рамках данной функции после кода для вращения акторов:

scale += 0.01;
if (scale > 1.0) scale = 0;
float scale_amount = smooth_step2(1.0, 2.0, scale);

clutter_actor_set_scale(rect1, scale_amount, scale_amount);
clutter_actor_set_scale(rect2, scale_amount, scale_amount);
clutter_actor_set_scale(rect3, scale_amount, scale_amount);
clutter_actor_set_scale(rect4, scale_amount, scale_amount);
clutter_actor_set_scale(rect5, scale_amount, scale_amount);
clutter_actor_set_scale(rect6, scale_amount, scale_amount);

Функция smooth_step2() является одним из небольших хаков, которые реализованы в рамках подключенного файла mathhacks.h и предназначена для возврата значения из диапазона от X до Y на основе значения Z, где значение Z находится в диапазоне от 0 до 1. В нашем примере выше мы будем получать значение из диапазона от 1.0 до 2.0 в зависимости от масштаба.

Цифра "2" в имени функции используется потому, что в том случае, если значение "масштаба" меньше 0.5, функция будет осуществлять интерполяцию в диапазоне от 1.0 до 2.0; при этом если значение "масштаба" больше 0.5, функция будет осуществлять интерполяцию в диапазоне от 2.0 до 1.0. По существу это означает, что наши прямоугольники будут красиво и плавно увеличивать свой размер в 2 раза и после этого уменьшать его до исходного. Мы также включили в заголовочный файл простую функцию smooth_step(), которая не возвращает оригинальное значение.

Это все, что нужно для изменения масштаба акторов - скомпилируйте исходный код приложения, запустите его и вы увидите вращающиеся и расширяющиеся/сжимающиеся в процессе анимации прямоугольники. Это было просто? Определенно, но мы только начали работу, поэтому давайте перейдем к рассмотрению более интересного аспекта работы со сценой: выбора акторов.

Как определить, над каким из акторов была нажата кнопка мыши при наличии большого количества акторов на сцене? В случае прямоугольников данный вопрос сводится к более простому вопросу: "находится ли данная точка в границах прямоугольника?", который имеет смысл лишь в том случае, если прямоугольник не масштабируется и не вращается, но в при использовании упомянутых преобразований ситуация становится более сложной. К счастью, в рамках проекта Moblin был разработан вспомогательный механизм: мы можем сообщить библиотеке Clutter о необходимости вызова функции в момент нажатия кнопки мыши, после чего обратиться к ней же за информацией о том, какой актор находился под указателем мыши в этот момент. Это звучит достаточно просто из-за того, что реализация данной последовательности действий на самом деле является достаточно простой - следует начать с размещения этой строки сразу же после последнего вызова функции create_rect() в коде функции main():

g_signal_connect(stage, "button-press-event", G_CALLBACK(on_stage_button_press), NULL);

Данный вызов позволяет связать сигнал button-press-event с новой функцией обратного вызова с именем on_stage_button_press(). Как и в случае функции обратного вызова для обработки нового кадра шкалы времени, данная функция должна принимать очень специфические параметры, поэтому вы также можете копировать и вставлять ее объявление по мере надобности. В рамках данной функции мы попытаемся a) запросить у библиотеки Clutter информацию о координатах X и Y, которые соответствуют позиции указателя мыши в момент нажатия ее кнопки, b) использовать эти координаты для установления того, какой актор находился под указателем мыши в момент нажатия кнопки мыши, после чего c) скрыть этот актор.

Ниже приведен код этой функции обратного вызова; помните о том, что она должна находиться до функции main() в файле исходного кода вашего приложения:

void on_stage_button_press(ClutterStage *stage, ClutterEvent *event, gpointer data) {
        // определение позиции курсора мыши на экране в момент нажатия ее кнопки
        gfloat x = 0;
        gfloat y = 0;
        clutter_event_get_coords(event, &x, &y);

        // определение актора, который находился под курсором мыши в момент нажатия ее кнопки
        ClutterActor* clicked = clutter_stage_get_actor_at_pos(CLUTTER_STAGE(stage), CLUTTER_PICK_ALL, x, y);

        // следует игнорировать нажатия над поверхностью сцены
        if (clicked == CLUTTER_ACTOR(stage)) return;

        // скрытие актора, который находился под курсором мыши в момент нажатия ее кнопки
        clutter_actor_hide(clicked);
}

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

Clutter может масштабировать наши прямоугольники в процессе их вращения без каких-либо искажений. Слава OpenGL!

Clutter может масштабировать наши прямоугольники в процессе их вращения без каких-либо искажений. Слава OpenGL!

Работа с множествами шкал времени

Наша шкала времени позволяет нам анимировать объекты сцены произвольным образом, но она не всегда удобна - все операции должны размещаться в рамках одной функции, что приводит к неразберихе при реализации сложных анимаций. И снова библиотека Clutter приходит на помощь, представляя концепцию множеств шкал времени (scores): коллекций шкал времени, которые могут работать как параллельно, так и последовательно в зависимости от ваших потребностей (соответствующий класс объявлен устаревшим в версии Clutter 1.8 и не рекомендуется к использованию в новых приложениях - примечание переводчика). Для демонстрации примера использования множеств шкал времени в первую очередь следует разделить нашу текущую функцию on_timeline_new_frame() на две части - одна часть будет осуществлять вращение акторов, другая - их масштабирование. Данные функции должны выглядеть следующим образом:

void on_timeline_rotation_new_frame(ClutterTimeline *timeline, gint frame_num, gpointer data) {
        rotation += 0.3;

        clutter_actor_set_rotation_angle(rect1, CLUTTER_Z_AXIS, rotation * 5);
        clutter_actor_set_rotation_angle(rect2, CLUTTER_Z_AXIS, rotation * 4);
        clutter_actor_set_rotation_angle(rect3, CLUTTER_Z_AXIS, rotation * 3);
        clutter_actor_set_rotation_angle(rect4, CLUTTER_Z_AXIS, rotation * 2);
        clutter_actor_set_rotation_angle(rect5, CLUTTER_Z_AXIS, rotation);
        clutter_actor_set_rotation_angle(rect6, CLUTTER_Z_AXIS, rotation * 0.5);
}

void on_timeline_scale_new_frame(ClutterTimeline *timeline, gint frame_num, gpointer data) {
        scale += 0.01;
        if (scale > 1.0) scale = 0;
        float scale_amount = smooth_step2(1.0, 2.0, scale);
        
        clutter_actor_set_scale(rect1, scale_amount, scale_amount);
        clutter_actor_set_scale(rect2, scale_amount, scale_amount);
        clutter_actor_set_scale(rect3, scale_amount, scale_amount);
        clutter_actor_set_scale(rect4, scale_amount, scale_amount);
        clutter_actor_set_scale(rect5, scale_amount, scale_amount);
        clutter_actor_set_scale(rect6, scale_amount, scale_amount);
}

В данных функциях нет какого-либо нового кода - существующая функция была всего лишь разделена на две функции. Теперь мы можем восстановить работоспособность приложения, используя множество шкал времени путем создания этого множества, добавления обоих шакал времени в него и активации этих шкал времени в параллельном режиме. В данном случае нам не понадобится функция clutter_timeline_set_repeat_count(), так как нам не придется переводить шкалы времени в циклический режим - вместо этого нам придется перевести множество шкал времени в циклический режим. Схема последовательного именования функций библиотеки Clutter не позволит просто предугадать имя функции clutter_score_set_loop(), которая позволит перевести в циклический режим множество шкал времени, а не отдельные шкалы времени.

Как и отдельные шкалы времени, множества шкал времени должны уничтожаться в ручном режиме - они не уничтожаются автоматически при уничтожении сцены. Исходя из этого, следует модифицировать последние строки функции main() следующим образом:

// создание множества шкал времени и перевод его в циклический режим
ClutterScore *score = clutter_score_new();
clutter_score_set_loop(score, TRUE);

// создание новой шкалы времени и установка длительности кадра анимации, равной половине секунды
ClutterTimeline *timeline_rotation = clutter_timeline_new(500);
g_signal_connect(timeline_rotation, "new-frame", G_CALLBACK(on_timeline_rotation_new_frame), NULL);

// добавление шкалы времени в множество шкал времени
clutter_score_append(score, NULL, timeline_rotation);

// создание второй шкалы времени, с такой же длительностью кадра анимации, равной половине секунды
ClutterTimeline *timeline_scale = clutter_timeline_new(500);
g_signal_connect(timeline_scale, "new-frame", G_CALLBACK(on_timeline_scale_new_frame), NULL);

// добавление шкалы времени в множество шкал времени по аналогии с предыдущей шкалой
clutter_score_append(score, NULL, timeline_scale);

// активация множества шкал времени
clutter_score_start(score);

clutter_main();

// освобождение зарезервированной памяти
g_object_unref(timeline_rotation);
g_object_unref(timeline_scale);
g_object_unref(score);

return EXIT_SUCCESS;

Теперь вы можете скомпилировать код и выполнить приложение для того, чтобы убедиться в том, что ничего не изменилось: прямоугольники продолжат вращаться и изменять масштаб также, как это было ранее. Причина отсутствия изменений состоит в том, что мы добавили шкалы времени в множество шкал времени и сообщили библиотеке Clutter о том, что шкалы времени должны работать в параллельном режиме - после однократного вызова функции on_timeline_rotation_new_frame(), происходит однократный вызов функции on_timeline_scale_new_frame() и.т.д.

Но что делать в случае необходимости проигрывания с помощью библиотеки Clutter 500мс анимации с помощью функции on_timeline_rotation_new_frame() перед проигрыванием 500мс другой анимации с помощью функции on_timeline_scale_new_frame()? Для этого нам придется просто модифицировать вызов функции clutter_score_append(), а именно ее второй параметр, в качестве которого часто используется значение NULL, которое означает "родительской шкалы времени не существует - следует использовать шкалу времени при каждой возможности". Попытайтесь модифицировать второй вызов функции clutter_score_append() следующим образом:

clutter_score_append(score, timeline_rotation, timeline_scale);

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

Больше, чем разноцветные прямоугольники

Как мы говорили ранее, библиотека Clutter предоставляет в распоряжение разработчика универсальные акторы, но магия состоит в том, что мы можем использовать их для реализации различных элементов сцены. Например, если вы хотите, чтобы в вашем приложении вместо обычных прямоугольников вращались и изменяли масштаб изображения, все что вам понадобится - это заменить строку на блок кода в функции create_rect() (также не забудьте подключить к коду вашего приложения заголовочный файл gdk-pixbuf/gdk-pixbuf.h - примечание переводчика), т.е., вместо создания прямоугольника с указанием цвета мы можем создать текстуру с помощью следующей последовательности вызовов:

ClutterActor *rect = clutter_actor_new();
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file("media/card_1.png", NULL);
ClutterContent *image = clutter_image_new();
clutter_image_set_data(CLUTTER_IMAGE(image),
                      gdk_pixbuf_get_pixels(pixbuf),
                      gdk_pixbuf_get_has_alpha(pixbuf)
                      ? COGL_PIXEL_FORMAT_RGBA_8888
                      : COGL_PIXEL_FORMAT_RGB_888,
                       gdk_pixbuf_get_width(pixbuf),
                       gdk_pixbuf_get_height(pixbuf),
                       gdk_pixbuf_get_rowstride(pixbuf),
                       NULL);
g_object_unref(pixbuf);
clutter_actor_set_content(rect, image);
g_object_unref(image);

Более ничего не следует изменять: если вы скомпилируете и запустите программу сейчас, вы увидите на экране текстуры вместо обычных прямоугольников.

Clutter может загружать текстуры из файлов изображений форматов PNG и JPEG и работает с ними также, как и с акторами любого другого типа

Clutter может загружать текстуры из файлов изображений форматов PNG и JPEG и работает с ними также, как и с акторами любого другого типа

Библиотека Clutter может работать даже с более сложными объектами, такими, как текстовые поля; замените строки кода для создания текстуры на следующую строку и убедитесь в том, насколько это просто:

ClutterActor *rect = clutter_text_new_full("Sans 24", "Hello, world", &col);

Да, в случае использования данного вызова наши разноцветные прямоугольники превратятся в разноцветные текстовые сообщения на экране, которые как и раньше будут вращаться и изменять масштаб. Первым параметром функции clutter_text_new_full() должна быть строка с именем и размером шрифта, который вы хотите использовать; вторым параметром должна быть строка с текстом, который должен быть выведен, а третьим параметром - используемый цвет шрифта.

Но вывод простого текста, который вы видите на экране - это еще не все возможности актора; на самом деле рассматриваемый актор является полноценным текстовым полем, позволяющим читать ввод с клавиатуры и вставлять символы перехода но новую строку при нажатии пользователем клавиши Enter. Для того, чтобы познакомиться с данной функцией актора нам придется внести в исходный код приложения два небольших изменения. Во-первых, следует разместить данную строку сразу же после строки с вызовом функции clutter_text_new_full() в рамках функции create_rect():

clutter_text_set_editable(CLUTTER_TEXT(rect), TRUE);

А теперь следует изменить реализацию функции on_stage_button_press() таким образом, чтобы вместо скрытия актора, над которым находился курсор мыши в момент нажатия ее кнопки, осуществлялся захват фокуса клавиатуры, благодаря которому пользователь мог бы начать ввод текста. Модифицируйте последние строки функции следующим образом:

ClutterActor* clicked = clutter_stage_get_actor_at_pos(CLUTTER_STAGE(stage), CLUTTER_PICK_ALL, x, y);
if (clicked == CLUTTER_ACTOR(stage)) return;
clutter_actor_grab_key_focus(clicked);

Библиотека Clutter может работать с текстом и даже позволяет редактировать его без каких-либо сложностей. Важное замечание: вы получите лучшие результаты при включении вертикальной синхронизации

Библиотека Clutter может работать с текстом и даже позволяет редактировать его без каких-либо сложностей. Важное замечание: вы получите лучшие результаты при включении вертикальной синхронизации

Финальные модификации кода

Перед тем, как мы подведем итоги данного непродолжительного погружения в мир Clutter, нам необходимо попытаться исправить три небольших недоработки. Во-первых, давно пора заменить заголовок окна приложения "./clutterapp" путем размещения следующей строки кода в функции main() перед вызовом функции clutter_actor_show():

clutter_stage_set_title(CLUTTER_STAGE(stage), "Вращающиеся прямоугольники!");

Во-вторых, даже несмотря на то, что вы могли не знать об этом, каждый актор библиотеки Clutter также может иметь дополнительные ассоциированные события нажатия кнопок мыши и ее перемещения, нажатия клавиш клавиатуры и другие. Ключевым требованием для активации механизма обработки данных событий является перевод актора в режим "восприимчивости к внешним воздействиям" (reactive mode), т.е., сообщение библиотеке Clutter о необходимости генерации событий для заданного актора. По умолчанию большинство акторов библиотеки Clutter не переведено в данный режим, так как он связан с чрезмерной тратой ресурсов центрального процессора. Исходя из вышесказанного, следует переводить акторы в режим "восприимчивости к внешним воздействиям" по мере необходимости, иначе вы будете долго искать причину, по которой для виджета не генерируются те или иные события.

Для создания "восприимчивого к внешним воздействиям" прямоугольника следует использовать следующий код:

ClutterActor *rect = clutter_actor_new();
clutter_actor_set_background_color(CLUTTER_ACTOR(rect), &col);
clutter_actor_set_size(rect, 256, 128);
clutter_actor_set_position(rect, 128, 128);
clutter_actor_set_reactive (rect, TRUE);
g_signal_connect (rect, "button-press-event", G_CALLBACK (on_rect_button_press), NULL);

Как вы видите, данный код связывает функцию on_rect_button_press() с объектом актора-прямоугольника. Этот код очень похож на код из рассмотренной ранее функции on_stage_button_press(), несмотря на то, что в данном случае (как вы можете представить) нам понадобится доступ к переменной типа ClutterActor для передачи ее в качестве первого параметра. В данном простом примере мы реализуем механизм изменения цвета прямоугольника при нажатии кнопки мыши; разместите эту реализацию функции в файле исходного кода вашего приложения:

gboolean on_rect_button_press(ClutterActor *actor, ClutterEvent *event, gpointer data) {
    ClutterColor newcol = { rand() % 256, rand() % 256, rand() % 256, 128 };
    clutter_actor_set_background_color(actor, &newcol);

    return TRUE;
}

Clutter передает указатель на объект актора, который находился под указателем мыши в момент нажатия ее кнопки, и это означает, что мы можем связать эту функцию с множеством различных объектов акторов-прямоугольников, при этом она будет изменять цвет лишь того прямоугольника, который находится под указателем мыши. Если вы хотите использовать стандартную систему выбора акторов, которая обсуждалась в ходе рассмотрения механизма обработки одиночных нажатий кнопки мыши, попытайтесь заменить CLUTTER_PICK_ALL на CLUTTER_PICK_REACTIVE для того, чтобы имелась возможность выбирать лишь "восприимчивые к внешним воздействиям" элементы сцены.

Наконец, мы пропустили большую часть описания механизмов анимации, так как данное описание может усложнить материал и не совсем укладывается в рамки данного руководства для начинающих разработчиков. Хотя было бы непростительно не упомянуть о том, что библиотека Clutter имеет особый механизм для упрощения анимации элементов сцены. Этот механизм доступен посредством функции с именем clutter_animate(), которая скрывает все сложности процесса анимации элементов сцены и настолько упрощает этот процесс, что в некоторых случаях он начинает доставлять удовольствие (данная функция объявлена устаревшей в версии Clutter 1.12 и не рекомендуется к использованию в новых приложениях - примечание переводчика).

Для достижения работоспособности данного примера нам придется убрать часть существующего кода - к примеру, созданные ранее шкалы времени в данном случае не будут нужны. В результате код примера должен выглядеть следующим образом:

#include <clutter/clutter.h>
#include <stdlib.h>

// шесть простых прямоугольников и сцена
ClutterActor *stage = NULL;
ClutterActor *rect1 = NULL;
ClutterActor *rect2 = NULL;
ClutterActor *rect3 = NULL;
ClutterActor *rect4 = NULL;
ClutterActor *rect5 = NULL;
ClutterActor *rect6 = NULL;

// функция create_rect() должна создавать прямоугольники, а не текстовые поля
ClutterActor *create_rect(ClutterColor col) {
  ClutterActor *rect = clutter_actor_new();
  clutter_actor_set_background_color(CLUTTER_ACTOR(rect), &col);
  clutter_actor_set_size(rect, 256, 128);
  clutter_actor_set_position(rect, 256, 256);
  clutter_actor_set_pivot_point(rect, 128, 64);
  clutter_actor_add_child(CLUTTER_ACTOR(stage), rect);
  clutter_actor_show(rect);

  return rect;
}

// мы добавим реализацию данной функции позднее
void on_stage_button_press (ClutterStage *stage, ClutterEvent *event, gpointer data) {
        // здесь должны обрабатываться нажатия кнопок мыши
}

int main(int argc, char *argv[]) {
  ClutterInitError state;
  
  state = clutter_init(&argc, &argv);
  
  if (state != CLUTTER_INIT_SUCCESS) return EXIT_FAILURE;
  
  ClutterColor stage_color = { 0, 0, 0, 255 };

  stage = clutter_stage_new();
  clutter_actor_set_size(stage, 512, 512);
  clutter_actor_set_background_color(CLUTTER_ACTOR(stage), &stage_color);

  ClutterColor red = { 255, 0, 0, 128 };
  ClutterColor green = { 0, 255, 0, 128 };
  ClutterColor blue = { 0, 0, 255, 128 };
  ClutterColor yellow = { 255, 255, 0, 128 };
  ClutterColor cyan = { 0, 255, 255, 128 };
  ClutterColor purple = { 255, 0, 255, 128 };

  rect1 = create_rect(red);
  rect2 = create_rect(green);
  rect3 = create_rect(blue);
  rect4 = create_rect(yellow);
  rect5 = create_rect(cyan);
  rect6 = create_rect(purple);

  g_signal_connect (stage, "button-press-event", G_CALLBACK (on_stage_button_press), NULL);
  clutter_stage_set_title(CLUTTER_STAGE(stage), "Вращающиеся прямоугольники!");
  clutter_actor_show(stage);

  clutter_main();

  return EXIT_SUCCESS;
}

Когда вы скомпилируете код и запустите приложение, вы увидите не подающие признаков жизни прямоугольники в центре сцены. Мы будем использовать функцию clutter_actor_animate() для перемещения каждого из этих прямоугольников в разные части сцены с вращением в процессе движения. Можно выделить большое количество достоинств функции clutter_actor_animate(), но следующие три являются ключевыми: 1) вы можете очень быстро изменять тип анимации, 2) вы можете анимировать несколько элементов сцены с помощью одного вызова функции и 3) если вы вызовите clutter_actor_animate() для заданного актора до момента окончания его предыдущей анимации, библиотека Clutter вполне корректно обработает эту ситуацию.

В последнем листинге функция, предназначенная для обработки нажатий кнопок мыши должна содержать комментарий "// здесь должны обрабатываться нажатия кнопок мыши". Разместите следующий код вместо этого комментария:

move_rect(rect1);
move_rect(rect2);
move_rect(rect3);
move_rect(rect4);
move_rect(rect5);
move_rect(rect6);

В данный момент мы не объявили функцию move_rect(), но вы можете обратить внимание на то, что мы передаем указатели на каждый из объектов наших акторов-прямоугольников в нее. Данная функция будет осуществлять перемещение прямоугольника в произвольную позицию экрана с произвольным вращением, но, что важно, мы должны задать значения трех соответствующих параметров с помощью чисел с плавающей точкой, а не целых чисел. В том случае, если вы используете целые числа, в ходе тестирования наверняка произойдет аварийное завершение работы вашей программы, поэтому по крайней мере приведите типы значений к типу float и все будет отлично работать.

А это код функции:

void move_rect(ClutterActor *rect) {
        clutter_actor_animate (rect,
                CLUTTER_LINEAR,
                1000,
                "x", (float)(rand() % 512),
                "y", (float)(rand() % 512),
                "rotation-angle-z", (float)(rand() % 360),
                NULL);
}

Мы разделили вызов функции clutter_actor_animate() на несколько строк для того, чтобы было проще описать принцип работы этой функции. Давайте рассмотрим параметры функции по отдельности:

  • CLUTTER_LINEAR является типом анимации. Данная константа соответствует движению элемента сцены с постоянной скоростью в течение всего времени анимации.

  • Значение 1000 является временем анимации в миллисекундах.

  • Строка, начинающаяся с "x", предназначена для установки координаты конечной позиции актора по оси X, в данном случае случайного значения. На самом деле "x" является свойством актора, заданным в формате строки. Тоже самое относится и к строке, начинающейся с "y".

  • Строка, начинающаяся с "rotation-angle-z" предназначена для вращения актора.

  • После указания всех свойств актора, которые вы желаете изменить, вы должны использовать значение NULL.

Если вы запустите программу сейчас, вы обнаружите, что при нажатии кнопки мыши в любой точке сцены все прямоугольники начинают двигаться. Но это немного скучно, не так ли? Попробуйте заменить CLUTTER_LINEAR на CLUTTER_EASE_IN_OUT_BACK, скомпилировать и выполнить программу снова - мы не будем говорить о том, что произойдет, но нам кажется, что вы оцените это...

Прямоугольники, которые красиво летают по экрану; вместо них могут быть текстуры, текст или акторы любых других типов, которые вы добавите в Clutter

Прямоугольники, которые красиво летают по экрану; вместо них могут быть текстуры, текст или акторы любых других типов, которые вы добавите в Clutter

На этом все... на данный момент

Эта статья является всего лишь кратким руководством по работе с библиотекой Clutter, но мы считаем, что нам удалось затронуть все наиболее важные темы в течение нескольких часов, потраченных на ее написание. По крайней мере, теперь вам должны быть ясны мотивы, по которым руководство компании Caninical приняло решение об использовании Clutter для разработки продукта Netbook Remix GUI - это в действительности самый быстрый и простой способ реализации удобных и привлекательных пользовательских интерфейсов, причем библиотека выполняет большую часть сложной работы за вас. Фактически, сейчас после взгляда на интерфейс UNR вы должны подумать "я и сам наверняка мог бы сделать это." И, что особенно важно, вы наверняка сможете сделать это.