Пробуем 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 КБ, в основном за счёт картинок, кода там не намного больше).
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 56
    • 0
      У меня вопрос — signals: это фишка компилятора или самого Qt. Просто интересно стало.
      • +1
        фишка Qt
        • 0
          Это фишка Qt. Сигналы потом преобразовываются в обычные функции, которые обрабатываются уже мета-объектом.
          • 0
            Это, своего рода, расширение языка от Qt. Для того что бы это работало, необходимо перед c++ компилятором натравить на исходники moc (Meta Object Compiler), являющийся частью Qt.
            • +1
              >Для того что бы это работало, необходимо перед c++ компилятором натравить на исходники moc (Meta Object Compiler), являющийся частью Qt.

              У читателей может сложиться впечатление, что это нужно делать руками ;)

              Все необходимые телодвижения с moc, uic (который генерирует GUI из XML-файлов, созданных в Дизайнере), компиляцией ресурсов и локализаций — всё это делается незаметно для пользователя, с помощью qmake.
              • 0
                Увы, во времена qmake, мне не приходилось еще Qt использовать на уровне c++…

                А вот во времена Qt 1.44 — точно помню, надо было правила в Makefile-ы писать, для преобразования .cpp в moc.cpp.
                • 0
                  Ну не обязптельно qmake. На базе cmake`а можно создать qt проект и cmake тоже сам будет moc вызывать, да и прочие сборочные системы поддаются обучению. Но мне cmake милее всех:)
                  • 0
                    Можно, конечно, и CMake использовать, и другие системы (у меня у самого есть Qt-проект на CMake), но для него всё же приходится совершать дополнительные телодвижения (QT4_WRAP_UI, QT4_AUTOMOC и так далее). А для qmake этого не надо — он сам найдёт среди хедеров и сорцов те, которые нужно «мокнуть» и «мокнет» без дополнительного напоминания.
                    В этом и состоит разница между универсальным и специализированным инструментами :)
                    • 0
                      а я так и не понял в чем cmake может выиграть перед qmake. Ведь с qmake чтобы собрать программу на qt нам надо сделать лишь несколько простых действий.
                      qmake
                      make

                      а для cmake приходится еще CMakeList.txt писать и постоянно дополнять.
                      Может вы проясните в чем плюсы одного перед другим?
                      • +1
                        помимо няшного вывода сборки ^_^
                        • 0
                          Перед qmake надо сделать qmake -project, а потом руками вычищать то, что не надо было добавлять в проект и постоянно дополнять тем, что надо.

                          CMake предоставляет гораздо больше возможностей. От проверки наличия различных библиотек и запроса номера SVN-ревизии до сборки готовых пакетов (с расширением CPack). Причём в зависимости от платформы по одному и тому же конфигу будет собираться deb, rpm или инсталлятор для Windows.
                          • 0
                            o_O как…
                            а вы не в курсе, qtcreator поддерживает автообновление файла CMakeList.txt при обновлении проекта?
                            • 0
                              Нет. Только свои .pro.
                            • 0
                              Да действительно cmake более фичаст. Кроме того qmake -project в чистом виде годится только для простых qt-only программ. А если по человечески делать то разницы нет. Что .pro редактировать, что Cmakelists.txt. Кроме того cmake умеет программы инсталировать, больше генераторов, больше библиотек знает. Ну и универсален конечно.
                              • 0
                                Благодарю :) Давно уже хочу осилить cmake, все руки не доходят. Но похоже пора бы уже )
                              • 0
                                >Перед qmake надо сделать qmake -project, а потом руками вычищать то, что не надо было добавлять в проект и постоянно дополнять тем, что надо.

                                Не обязательно. Можно дописывать руками, как и в CMakeLists.txt. Просто .pro, как я уже сказал, сам делает кое-что из того, для чего в CMakeLists.txt нужно специально писать команды (но это издержки разницы подходов «универсальность-заточенность»).

                                >CMake предоставляет гораздо больше возможностей. От проверки наличия различных библиотек....

                                Открою секрет: qmake тоже умеет проверять библиотеки. Более того: даже умудряется проверять их версии. У меня в рабочем проекте, к примеру, в .pro файле определяется, какая версия EDS стоит, и в зависимости от этого делает те или иные вещи.

                                Насчёт остального — а линком не побалуете? А то я для проверки svn-ревизии вызываю и парсю «svn info» %)
                                Про пакеты, кстати, тоже очень заинтересовало.
                                • +2
                                  1) FindSubversion
                                  Subversion_WC_INFO( ${PROJECT_ROOT} SVN )
                                  ADD_DEFINITIONS( -DREVISION="${SVN_WC_REVISION}" )

                                  2) CPack — то ли расширение, то ли отдельная тулза, продолжающая идеи CMake в направлении автоупаковки. Сам только начал разбираться.
                                  • 0
                                    Класс, спасибо!
                                    «О, сколько нам открытий чудных...» :)
                        • 0
                          Красивое решение, не стали городить миллион строк препроцессорных директив, а просто добавили нужную функциональность через расширение, возьму на заметку.
                          • +1
                            Некоторые фреймворки вполне успешно реализуют то же самое на С++ вполне элегантно (пример здесь).
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • +3
                          Qt рулез. После того как на нее подсел — видеть ничего не хочу больше :)
                          • –2
                            Ну почему же, wxWidgets тоже неплох.
                            • +4
                              Честно, сидел на нем. Но не дотягивает уже к сожалению.
                              • +1
                                Уй, ну вы что? Это же невозможно сравнивать. wx как был десять лет назад набором плохо слепленных оберток с ограниченной функциональностью, наполненной багами и несовместимостями, так и остался.
                                • +4
                                  Когда я увидел документацию — закричал и выбросил монитор в окно.
                                  • –4
                                    Не надо, на мой взгляд, в wxWidgets документация в три с половиной раза лучше, чем у Qt.
                                    • +2
                                      Для сравнения, как оформленны описания почти одинаковых по логике объектов в обоих тулкитах:
                                      qt.nokia.com/doc/4.6-snapshot/qobject.html
                                      docs.wxwidgets.org/stable/wx_wxobject.html#wxobject

                                      ИМХО у Qt документация более читабельна. И охватывает различные моменты использования более полно.
                                      • –2
                                        Дело вкуса, конечно, но я всё равно считаю, что у wxWidgets доки понятнее устроены.
                                • –4
                                  Только стоит страшных денех :)
                                  • +3
                                    С версии Qt 4.5 LGPL / Free версия позволяет разрабатывать коммерческий продукт абсолютно бесплатно. Коммерческая предоставляет лишь поддержку от тролей.

                                    Устал уже повторять.
                                    • 0
                                      А либы для других языков? Например, PyQT тоже стала LGPL?
                                      • 0
                                        • 0
                                          мой комментарий на эту тему habrahabr.ru/blogs/qt_software/69291/#comment_1972369
                                          • 0
                                            Отлично! Хорошие новости.
                                          • +1
                                            PyQt — самостоятельный проект, разрабатывающийся совсем другой компанией, но Вы говорите так, как будто это именно Qt «стоит страшных денех», а не PyQt.
                                            А вопросы насчёт стоимости PyQt — к компании Riverbank Computing :) Проскакивала информация, что Нокия начала свой проект PySide (ссылка выше) именно потому, что устала уговаривать RB выпустить PyQt под LGPL.
                                            • 0
                                              Меня интересует совокупная стоимость использования технологии. Совсем недавно проскакивали цифры стоимости использования QT для разработки своих программ. Там получалось все очень невесело.
                                              Если ситуация изменилась в сторону LGPL, то это очень хорошо.
                                              • 0
                                                >Меня интересует совокупная стоимость использования технологии. Совсем недавно проскакивали цифры стоимости использования QT для разработки своих программ.

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

                                                Поймите уже: PyQt — это не часть Qt (не «либа для другого языка», как Вы выразились), это другая технология (разработанная другой компанией). Со своей собственной стоимостью, никак не относящейся к «стоимости Qt».
                                    • +2
                                      Где же было QPropertyAnimation пол года назад?
                                      Мне пришлось писать что-то подобное,
                                      я писал сначала визуальный объект, а потом класс который содержал все Property объекта,
                                      и функцию какой передавалось две переменных с разными свойствами,
                                      в результате возвращается новый класс свойств в котором уже обработаны промежуточные значения между теми двумя входными переменными по определенной функции обработки.
                                      Пол года назад это мне бы намного сэкономило время :)
                                      Желающие посмотреть на один из объектов на базе моего механизма смотрите тут: www.youtube.com/watch?v=C-0jDSsBoDA&feature=player_embedded#t=40
                                      • 0
                                        Думаю, многие пытались сами писать подобные вещи. Я ещё на Qt3 в одном проекте пытался какую-никакую анимацию изображать. Году этак в 2004-м… :)
                                        • 0
                                          Да, всем есть что вспомнить :)
                                          Хорошо что Qt делает создание программ все проще
                                        • +2
                                          Пол года назад это было в Solutions, а еще раньше в Labs ;) Иногда полезно туда заглядывать, достаточно интересных проектов и разработок там.
                                          • –1
                                            на руки демонстранта страшно смотреть, у него буд-то паралич (
                                        • 0
                                          Прикольно!

                                          Вот только не нужно было мне спать на лекциях по прикладной теории цифровых автоматов: посмотрел исходники Вашего усовершенствованного проекта и запутался в состояниях :(

                                          Такой вопрос: если сделать в автомате пару тысяч состояний (без анимации, естественно), как это скажется на производительности?

                                          P.S. Спасибо за статью.
                                          • 0
                                            >Такой вопрос: если сделать в автомате пару тысяч состояний (без анимации, естественно), как это скажется на производительности?

                                            Не знаю, на таких больших объёмах не тестировал.
                                          • 0
                                            И еще вопрос: как сделать так, чтобы во второй версии Вашей программы «выезжающая» картинка была поверх «заезжающей»? Добавить к состоянию еще одно свойство?

                                            О, и еще: для чего может использоваться безусловный переход состояний?
                                            • 0
                                              >И еще вопрос: как сделать так, чтобы во второй версии Вашей программы «выезжающая» картинка была поверх «заезжающей»?

                                              Думаю, нужно каким-то образом расположить выезжающий на самый верх стека виджетов (так называемый z-index им назначается в порядке из создания). Если честно, такой проблемой ни разу не заморачивался.

                                              >О, и еще: для чего может использоваться безусловный переход состояний?

                                              Если мы, к примеру, захотим, чтобы картинка сначала выехала, а потом изменила размер, то разносим это по двум разным состояниям, и переход между ними делаем безусловным. Получится так: кликаем — пошёл процесс перемещения, после завершения которого сразу же начнётся процесс изменения размера без повторного клика.
                                              • 0
                                                А, я въехал. Надо было мне сразу вспомнить про анимацию во flash. Тут почти прямая аналогия: состояния автомата — это что-то типа ключевых кадров на таймлайне, только круче, потому что переход может происходить по событиям. Невыспавшийся был… не увидел очевидных вещей.

                                                Спасибо за ответы :)
                                            • +1
                                              Замечательная статья.

                                              Скажите, а в чём преимущество использования QStateMachine перед стандартным connect?
                                              • 0
                                                В том что это более логичный и абстрактный способ описания зависимостей переходов интерфейса из одного состояния в другое, при наступлении определенных событий. Избавляет от рутины в виде создания кучи сигналов/слотов и связывания их. Эту задачу на себя берет QStateMachine. А еще лучше посмотреть документацию по нему, там есть примеры различных моделей конечного автомата, которые можно реализовать, и не все тривиальны, но легко укладываются в логику.
                                                • 0
                                                  Пользовательские сигналы/слоты всё равно вручную писать придётся. А в случае стандартных сигналов/слотов для connect тоже ничего не надо было добавлять.

                                                  Т.е., я почему спросил, потому что я так понимаю, что объём работы и там и там однаков.

                                                  Дело только в том, что конечный автомат идеологически более правильное и красивое решение. А технически и функционально никаких отличий от connect'a я не вижу.
                                                  • 0
                                                    IMHO не совсем одинаковое количество:
                                                    • 0
                                                      Случайно отправилось :(

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

                                                      • 0
                                                        > Вот сколько бы нужно было писать кода

                                                        Сколько? :)
                                                      • 0
                                                        >Т.е., я почему спросил, потому что я так понимаю, что объём работы и там и там однаков.

                                                        Не совсем. В случае state machine количество сигналов равно количеству переходов и никак не зависит от количества объектов. В случае ручных коннектов Вам нужно будет добавлять коннекты для каждого нового объекта.
                                                        Или я не до конца понял Вашу мысль?

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