Qt
September 2009 11

Пробуем Qt 4.6: Qt Animations и State Machine

image
На днях вышло так называемое «технологическое превью» (technological preview) Qt 4.6, которое позволяет уже сейчас попробовать новые фичи, которые войдут в релиз 4.6 этого замечательного фреймворка. Перечислять новшества я не буду — они были достаточно хорошо освещены в этом топике, а подробнее остановлюсь на двух из них: State Machine и Qt Animation Framevork.

Итак, что же они из себя представляют?

State Machine — это ничто иное, как конечный автомат: некий объект с заранее заданным конечным числом состояний и переходами между ними.
В Qt 4.6 реализовано именно так: мы можем создать нужное нам число состояний, и для каждого состояния указать, какие свойства имеет каждый объект в данном состоянии, после чего задать переходы между состояниями, указать стартовую точку и запустить автомат на исполнение.

Но для начала нам потребуется сам Qt 4.6. Я расскажу, как собрать его из исходников для Linux (применимо для любого дистрибутива). Сборка под Windows, думаю, мало отличается, если предварительно установить MinGW. Возможно, собрать можно и с помощью MSVS, но сам я не пробовал.

Сборка Qt 4.6


Я расскажу о своём способе сборки Qt 4.6 без какого-либо «негативного влияния» на текущую версию, установленную из репозитория. Возможно, мой способ не самый оптимальный, но по крайней мере он работает :)
Сразу предупреждаю: для сборки потребуется ~3 ГБ места на диске, а потом ещё ~900 МБ для установки (всё, что останется от сборки, потом можно будет удалить). Занимает так много, потому что во-первых содержит дебаг-информацию (т.к. это ещё не релиз), плюс это не просто библиотеки, но также примеры, документация и т.д.
Для начала скачиваем исходный код отсюда (tar.gz) или отсюда (zip). Распаковываем, переходим в каталог с исходниками, запускаем конфигурирование
$ ./configure --prefix=/opt/Qt4.6

Указывая подобный префикс, мы убиваем двух зайцев: 1) никак не влияем на уже установленную версию; и 2) можем впоследствии удалить всё «лёгким движением руки».
В процессе конфигурирования зададут вопрос о лицензии: отвечаем «о» (open source версия, включающая GPL и LGPL), соглашаемся с ней (говорим «yes»), ждём несколько минут. После окончания нам ещё раз напомнят, с каким префиксом было произведено конфигурирование.
Ну и, наконец, сборка! Запускаем
$ make

И ждём. Ждём. Ждём…
Пока идёт сборка, можно не просто сходить заварить себе чаю/кофе, но и заняться уборкой в комнате: на моём четырёхъядернике сборка в 4 потока заняла 40 минут. Количество потоков (по числу ядер процессора) можно указать при помощи опции "-j":
$ make -j 4

После окончания сборки устанавливаем с помощью
$ sudo make install

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

Настройка


Теперь нам нужно получить возможность использовать новую версию вместо той, что установлена в системе. Всё, что нам для этого нужно — это использовать правильный qmake, а он уже сам подхватит нужные инклюды и библиотеки и сгенерирует Makefile с их использованием.
Тут у нас есть 3 различных варианта:
  1. каждый раз вызывать qmake с полным указанием пути:
    $ /opt/Qt4.6/bin/qmake

  2. установить путь для qmake на время терминального сеанса:
    $ export PATH=/opt/Qt4.6/bin:$PATH

  3. прописать строку выше в ~/.bashrc, и тогда этот qmake будет использоваться всегда

Я использую второй вариант — для экспериментов это более, чем достаточно.

Пробуем


Для проверки того, что мы всё сделали правильно, скомпилируем небольшую программку:
$ mkdir qt_anim
$ cd qt_anim
$ (vim|emacs|nano|kate|gedit|juffed|...) anim.cpp

anim.cpp:
Copy Source | Copy HTML<br/><br/>#include <QApplication><br/>#include <QWidget><br/> <br/>int main(int argc, char* argv[]) {<br/>    QApplication app(argc, argv);<br/>    QWidget w;<br/>    w.resize(200, 100);<br/>    w.show();<br/>    return app.exec();<br/>}<br/> <br/>

$ qmake -project
$ qmake
$ make
$ ldd qt_anim | grep Qt


После этого нам должны отрапортовать, что используются те библиотеки, на которые мы рассчитываем. Если нет — убедитесь, что используется нужная версия qmake:
$ which qmake

и если версия не та — проверьте, что вы не ошиблись с настройкой PATH.

Начинаем!


Для начала поймём, что такое State Machine и с чем её едят. Ставим задачу: создать окно, в углу которого будет отображаться маленькая картинка, которая при клике по окну будет перемещаться и увеличиваться в размерах.

anim.cpp:
Copy Source | Copy HTML<br/>#include <QApplication><br/>#include "AnimWidget.h" <br/> <br/>int main(int argc, char* argv[]) {<br/>    QApplication app(argc, argv);<br/>    AnimWidget w;<br/>    w.resize(300, 300);<br/>    w.show();<br/>    return app.exec();<br/>}<br/> <br/> <br/>


AnimWidget.h:
Copy Source | Copy HTML<br/>#ifndef __ANIM_WIDGET_H__<br/>#define __ANIM_WIDGET_H__<br/> <br/>#include <QLabel><br/>#include <QStateMachine><br/>#include <QWidget><br/> <br/>class AnimWidget : public QWidget {<br/>Q_OBJECT<br/>public:<br/>    AnimWidget();<br/> <br/>signals:<br/>    /* signal for triggering state machine changes */<br/>    void clicked();<br/> <br/>protected:<br/>    /* here we will be catching clicks */<br/>    virtual void mouseReleaseEvent(QMouseEvent*);<br/> <br/>private:<br/>    QStateMachine machine_;<br/>    QLabel* photo_;<br/>};<br/> <br/>#endif // __ANIM_WIDGET_H__<br/> <br/>


AnimWidget.cpp:
Copy Source | Copy HTML<br/>#include "AnimWidget.h"<br/> <br/>#include <QState><br/>#include <QVariant><br/> <br/>AnimWidget::AnimWidget() {<br/>    photo_ = new QLabel("", this);<br/>    photo_->setGeometry( 0,  0, 40, 40);<br/>    photo_->setScaledContents(true);<br/>    photo_->setPixmap(QPixmap("ottawa.png"));<br/> <br/>    /* creating 2 states */<br/>    QState* st1 = new QState();<br/>    QState* st2 = new QState();<br/> <br/>    /* defining photo's properties for each of them */<br/>    st1->assignProperty(photo_, "geometry", QRect( 0,  0, 40, 40));<br/>    st2->assignProperty(photo_, "geometry", QRect(50, 50, 200, 200));<br/> <br/>    /* define transitions between states by clicking on main window*/<br/>    st1->addTransition(this, SIGNAL(clicked()), st2);<br/>    st2->addTransition(this, SIGNAL(clicked()), st1);<br/> <br/>    /* adding states to state machine */<br/>    machine_.addState(st1);<br/>    machine_.addState(st2);<br/>    machine_.setInitialState(st1);<br/> <br/>    /* starting machine */<br/>    machine_.start();<br/>}<br/> <br/>void AnimWidget::mouseReleaseEvent(QMouseEvent*) {<br/>    emit clicked();<br/>}<br/> <br/>


В строках
st1->assignProperty(photo_, "geometry", QRect(0, 0, 40, 40));
st2->assignProperty(photo_, "geometry", QRect(50, 50, 200, 200));

задаются свойства объектов, присущие им в том или ином состоянии. Стандартные Qt-шные классы содержат определённый набор свойств, а для задания свойств для ваших собственных классов используйте Q_PROPERTY (см. документацию).

Строки
st1->addTransition(this, SIGNAL(clicked()), st2);
st2->addTransition(this, SIGNAL(clicked()), st1);

определяют переходы между состояниями и условия для этих переходов. В нашем случае условием перехода будет являться сигнал «clicked()», брошенный главным окном.
Переход между состояниями может быть и безусловный — тогда не указывается объект и сигнал, а просто указывается оконечное состояние (см. документацию).

И не забудьте указать другую картинку вместо «ottawa.png» и положить рядом с исходниками (или же скачайте исходники по ссылке ниже).

Собираем:
$ qmake -project
$ qmake
$ make
$ ./qt_anim


Кликаем — прыгает!
Но прыгает моментально. Да, переход между состояниями машины происходит мгновенно — но это ведь не то, что мы хотим, верно? Давайте добавим немного анимации для перехода между состояниями. Для этого добавим следующий код перед строкой «machine_.start();»:
Copy Source | Copy HTML<br/>...<br/>/* adding animation */<br/>QPropertyAnimation* an1 = new QPropertyAnimation(photo_, "geometry");<br/>machine_.addDefaultAnimation(an1);<br/>...<br/> <br/>

и не забудем добавить
#include <QPropertyAnimation>
в начало файла.

Тут, в принципе, всё понятно: создаём анимацию для изменения атрибута «geometry» объекта «photo_» и добавляем анимацию в машину.

Собираем, запускаем…

Так намного лучше, правда? Картинка не прыгает, а плавно едет на новое место, изменяясь в размерах.

Можно менять различные параметры анимации, в частности, задавать длительность и «кривую ускорения». Можно сделать, чтобы картинка начинала движение быстро, а потом замедлялась; можно и наоборот, чтобы начинала медленно, а потом разгонялась; а можно и так, чтобы начинала медленно, разгонялась, а потом плавно тормозила. Возможных вариантов — несколько десятков (см. документацию). Я выбрал вариант «разгон — торможение» с кубическим изменением скорости. Добавим следующие строки после создания анимации:
Copy Source | Copy HTML<br/>...<br/>/* customizing the animation */<br/>an1->setEasingCurve(QEasingCurve::InOutCubic);<br/>an1->setDuration(700);<br/>... <br/>


Ну вот, теперь вместо скучного движения с постоянной скоростью, картинка двигается плавно и величаво :)

Собственно говоря, на этом моя статья подходит к концу, а дальше дело остаётся только за вашей фантазией. Применяя этот простой механизм «определяем множество состояний — назначаем свойства объектов для каждого состояния — определяем переходы между состояниями — задаём анимации для переходов», можно добиться впечатляющих результатов.

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

Исходники проекта можно взять тут (66 КБ).

Update: добавил ещё один небольшой проект, сделанный на базе первого (368 КБ, в основном за счёт картинок, кода там не намного больше).
+50
11.8k 38
Comments 56
Top of the day