Pull to refresh

Qt + Kinetic = Eye-candy за полчаса

Qt *
В процессе разработки новой версии QutIMа возникла потребность переписать систему уведомлений, так как старая имела множество недостатков.

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

Небольшое видео с демонстрацией возможностей:




Вступление

В обзорной статье был обойден стороной один достойный фреймворк, найденный мной на просторах Qt.Labs, а именно Kinetic.
Авторы обещают нам следующее:
  • Анимационный фреймворк — возможность создания как простых, так и сложных видов анимации для любых типов виджетов. Предоставление готовой анимации, возможность добавления и создания пользовательской.
  • Декларативное создание интерфейса — задание интерфейса путем описания его свойств, а не манипуляций с готовыми шаблонами.
  • Продвинутые графические возможности — добавление теней, прозрачности, фильтров, итд.
Выглядит вкусно, так ведь? Поэтому я, вооружившись терпением и готовностью к неизвестному, написал небольшой пример. Замечу, что времени на это ушло гораздо меньше ожидаемого.
Перемещения уведомлений получились плавными. Cами они выстраиваются всегда так, чтобы не перекрывать соседние, а в случае, когда текст не влазит, окошко автоматически отмасштабируется.

Что нужно, для того, чтобы создать библиотеку, которая умеет выводить данные уведомления?
  • KineticNotifications — посредством этого класса и будут отсылаться уведомления. Класс отвечает за отображение и анимацию уведомлений.
  • NotificationWidget — памо всплывающее уведомление, представляет собой QTextBrowser с убраной рамкой и добавленым альфа каналом.
  • Notification Manager — синглтон, который хранит указатели на все запущенные уведомления, управляет их размещением, а также предоставляет настройки.
Код программы

Для того, чтобы вызвать уведомление, достаточно пары строк:
  1. KineticNotification *notify = new KineticNotification("id", timeout);
  2. notify->setMessage(QString::fromUtf8("Тест"),
  3.                    QString::fromUtf8("Сообщение"),
  4.                    "./images/avatar.jpg"
  5.                    );
  6. connect(notify, SIGNAL(action2Activated()), QApplication::instance(), SLOT(quit()));
  7. connect(notify, SIGNAL(action1Activated()), SLOT(nextNotify()));
  8. notify->send();

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

Делается это весьма нехитрым образом:
  1. KineticNotification *notify = NotificationsManager::self()->getById("id");
  2. if ( notify != 0 ) {
  3.     notify->appendMessage(QString::fromUtf8("Добавляем строку текста"));
  4. }

Важно помнить, что в случае отсутствия уведомления с запрашиваемым id, функция вернёт 0.

Теперь перейдем к самому главному: к описанию работы State машины.
Для начала необходимо создать все состояния машины. Всего их будет три:
  • show_state — уведомление показано на экране
  • hide_state — уведомление скрыто
  • final_state — это положение останавливает state машину
Зададим последовательность состояний:

  1. show_state->addTransition(notification_widget, SIGNAL(action1Activated()), hide_state);
  2. show_state->addTransition(notification_widget, SIGNAL(action2Activated()), hide_state);
  3. hide_state->addTransition(hide_state, SIGNAL(polished()), final_state);
  4. show_state->addTransition(this, SIGNAL(updated()), show_state); //небольшая хитрость

Последняя строчка позволяет обойтись без дополнительного состояния машины при необходимости изменить положение на экране отрисованного ранее уведомления. После чего нужно задать начальную позицию для уведомления (за правым праем экрана) и позицию, которую уведомление примет, перейдя в состояние show_state. Положение и размер виджета можно задать через метод setGeometry, в качестве аргумента которого выступает QRect, который представляет собой ни что иное, как прямоугольник, у которого задана координата верхней левой точки, а также длинна и высота.
Чтобы узнать, в какой точке экрана нужно создать уведомление и куда его потом перемещать нам необходимо знать положение предыдущего виджета или, в случае отсутствия такого, нижнего правого угла:
  1. QRect geom = QApplication::desktop()->availableGeometry(QCursor::pos());

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

Начальная точка получена, теперь можно задать текст, изменить высоту виджета на оптимальную, чтобы в него помещался весь отображаемый текст и картинки.
  1. this->document()->setHtml(data);
  2. this->document()->setTextWidth(NotificationsManager::self()->defaultSize.width());
  3. int width = NotificationsManager::self()->defaultSize.width();
  4. int height = this->document()->size().height();
  5.  
  6. return QSize(width,height);

Обязательно задаем какая стадия следует за какой и какое условие необходимо для перехода машины из одной стадии в другую
  1. if ( timeOut > 0 ) {
  2.     startTimer(timeOut);
  3.     show_state->addTransition(this, SIGNAL(timeoutReached()), hide_state);
  4. }
  5.  
  6. machine.addState(show_state);
  7. machine.addState(hide_state);
  8. machine.addState(final_state);
  9. machine.setInitialState (show_state);
  10.  

Но положение и размер уведомлений периодически приходится менять, например если нижний виджет скрылся, то необходимо сдвинуть все остальные уведомления вниз, дабы не создавать пустое пространство. То-же касается добавления текста: рано или поздно придется изменить размер виджета и, соответственно, сдвинуть все остальные, чтобы ни один из них не перекрывал другой. Помимо этого очень важно знать конечное положение уведомления, так как оно нужно для корректного пересчёта положения всей цепочки. В противном случае могут быть получены мгновенные положение и размер уведомления в процессе его движения, в результате чего виджеты примут неправильное положение.
Для обновления достаточно вызвать в синглтоне функцию, которая по новой пересчитает позиции виджетов от нижнего правого края экрана.
  1. NotificationsManager::self()->updateGeometry();

Вызовет в каждом из виджетов функцию обновления положения и размера.
  1. show_state->assignProperty(notification_widget, "geometry", geom);
  2. updateGeometry(geom);
  3. geom.moveRight(geom.right() + notification_widget->width() + NotificationsManager::self()->margin);
  4. hide_state->assignProperty(notification_widget, "geometry", geom);

Таким образом, достаточно при любом событии, которое может привести к изменению положения или размера виджета вызвать функцию обновления. К таким событиям относится удаление виджета и добавление в него дополнительных строк.
В целом получилось весьма функционально и достаточно лаконично.

Заключение

Для тех, кому интересна реализация виджета уведомлений, и кому интересно подробнее изучить работу библиотеки, а также воспользоваться ей, ссылка на исходники, лицензия LGPL. Данная программа тестировалась на Qt4.6tp1, kinetic бранче 4.5 ветки и должна работать с QAnimationFramework из Qt Solutions.
Приятного использования

N.B. Опубликовано по просьбе одного хорошего человека из команды разработчиков QutIM, который не имеет аккаунта на хабре, но страстно хотел бы участвовать в жизни сообщества. Вот его почта: sauron@citadelspb.com

UPD: Приветствуем Gorthauer87!
Tags:
Hubs:
Total votes 36: ↑33 and ↓3 +30
Views 2.8K
Comments Comments 41