diff options
Diffstat (limited to 'weather/src/citylist.cpp')
-rw-r--r-- | weather/src/citylist.cpp | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/weather/src/citylist.cpp b/weather/src/citylist.cpp new file mode 100644 index 0000000..d3ac7fd --- /dev/null +++ b/weather/src/citylist.cpp @@ -0,0 +1,465 @@ +#include "citylist.h" +#include "settings.h" +#include "pixmaploader.h" +#include <QPainter> +#include <cmath> +#include <QParallelAnimationGroup> +#include <QPropertyAnimation> + +#define LIST_TOP_PIXMAP (PixmapLoader::getPic("list_top")) + +#define ITEM_BACKGROUND (PixmapLoader::getPic("list_item_bg")) +#define SELECTED_ITEM_BACKGROUND (PixmapLoader::getPic("list_item_selected_bg")) + +#define ITEM_BUTTON_PIXMAP (PixmapLoader::getPic("button_list_delete")) +#define ITEM_BUTTON_LEFT (Settings::scaleWidth(406.0)) + +#define ITEM_CHECK_PIXMAP (PixmapLoader::getPic("list_check")) +#define ITEM_CHECK_LEFT (Settings::scaleWidth(10.0)) + +#define TEXT_LEFT (2 * ITEM_CHECK_LEFT + ITEM_CHECK_PIXMAP.width()) + +#define CITY_NAME_FONT_SIZE (Settings::scaleHeight(50.0)) + +static const int maxVisibleItems = 6; +static const int minListCount = 3; + +static inline qreal getCenterVerticalPos(QGraphicsItem *parent, QGraphicsItem *item) +{ + const qreal top = (parent->boundingRect().height() - item->boundingRect().height()) / 2; + return top - parent->boundingRect().top() - item->boundingRect().top(); +} + +static inline qreal getCenterVerticalPos(QGraphicsItem *item) +{ + return getCenterVerticalPos(item->parentItem(), item); +} + +// CityListScrollBox + +#ifdef QT_ARCH_ARM +static const int acceleration_factor = 12; +#else +static const int acceleration_factor = 64; +#endif + + +static const int max_gesture_time_ms = 500; +static const qreal degrees = 30.0; +static const qreal limit_angle_tg = tan(degrees * 3.141592 / 180.0); +static const qreal min_gesture_length = 100.0; + +CityListScrollBox::CityListScrollBox(QGraphicsItem *content, QGraphicsItem *parent) + : GestureBox(parent) + , m_content(content) + , m_scroll(new ScrollBar(this)) +{ + setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); + if (m_content) + m_content->setParentItem(this); + + m_scroll->setPos(Settings::scaleWidth(460.0), Settings::scaleHeight(32.5)); + + m_content->setZValue(0.0); + m_scroll->setZValue(1.0); +} + +void CityListScrollBox::gestureMousePress(QPointF pos, bool &startGesture, bool &acceptClick) +{ + Q_UNUSED(pos); + startGesture = true; + acceptClick = !m_ticker.isActive(); + if (m_ticker.isActive()) { + m_ticker.stop(); + m_speed = 0.0; + } +} + +void CityListScrollBox::gestureStart(QPointF pos) +{ + m_startPoint = pos; +} + +void CityListScrollBox::contentPositionUpdated() +{ + qreal height = boundingRect().height(); + qreal content = m_content->boundingRect().height(); + + if (height < content) { + qreal pos = content - height; + pos = -m_content->pos().y() / pos; + m_scroll->setValue(pos < 0.0 ? 0.0 : pos); + } else + if (m_content->pos().y() != 0.0) { + m_content->setPos(0.0, 0.0); + m_scroll->setValue(0.0); + } + +} + +bool CityListScrollBox::move(QPointF movement) +{ + if (m_content && m_content->boundingRect().height() > boundingRect().height()) { + + qreal dist = movement.y(); + + + if (dist > 0 && m_content->pos().y() < 0) { + qreal top = m_content->pos().y() + dist; + m_content->setPos(m_content->pos().x(), top > 0 ? 0 : top); + contentPositionUpdated(); + return true; + } + + if (dist < 0) { + qreal top = m_content->pos().y() + dist; + if (top + m_content->boundingRect().height() >= boundingRect().height()) { + m_content->setPos(m_content->pos().x(), top); + contentPositionUpdated(); + return true; + } + } + + } + return false; +} + +void CityListScrollBox::gestureMove(QPointF pos, QPointF movement, QPointF speed) +{ + Q_UNUSED(pos); + Q_UNUSED(speed); + move(movement); +} + +void CityListScrollBox::gestureEnd(QPointF pos, QPointF speed) +{ + qreal x = pos.x() - m_startPoint.x(); + qreal y = pos.y() - m_startPoint.y(); + if (fabs(x / y) > limit_angle_tg || fabs(y) < min_gesture_length) + return; + + qreal topSpeed = Settings::scaleHeight(1500.0); + m_acceleration = topSpeed / acceleration_factor; + m_speed = qBound(-topSpeed, speed.y(), topSpeed); + if (m_speed != 0) { + m_time = QTime::currentTime(); + m_ticker.start(20, this); + } +} + +void CityListScrollBox::timerEvent(QTimerEvent *event) +{ + bool stopTimer = true; + if (m_speed != 0.0) { + QTime now(QTime::currentTime()); + qreal movement = m_speed * (qreal)m_time.msecsTo(now) / 1000.0; + m_time = now; + stopTimer = !move(QPointF(0, movement)); + if (!stopTimer) { + m_speed = m_speed > 0.0 ? qMax(qreal(0.0), m_speed - m_acceleration) + : qMin(qreal(0.0), m_speed + m_acceleration); + stopTimer = m_speed == 0.0; + } + } + if (stopTimer) + m_ticker.stop(); + QObject::timerEvent(event); +} + +// CityListText + +CityListText::CityListText(const QString &text, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_painter(CITY_NAME_FONT_SIZE, Qt::white, text) +{ + setCacheMode(ItemCoordinateCache); + m_painter.setPos(0.0, 0.0); + m_painter.setMaxWidth(ITEM_BUTTON_LEFT - TEXT_LEFT); +} + +QRectF CityListText::boundingRect() const +{ + return QRectF(0.0, 0.0, m_painter.width(), m_painter.height()); +} + +void CityListText::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + m_painter.paint(painter); +} + +// CityListItem + +CityListItem::CityListItem(const ForecastData &data, CityContentList *list, bool deleteVisible) + : ContentListItem(0) + , m_height(ITEM_BACKGROUND.height()) + , m_data(data) + , m_list(list) + , m_background(new QGraphicsPixmapItem(ITEM_BACKGROUND, this)) + , m_check(new QGraphicsPixmapItem(ITEM_CHECK_PIXMAP, m_background)) + , m_delete(new PixmapButton(80.0, ITEM_BUTTON_PIXMAP, m_background)) + , m_text(new CityListText(m_data.cityName(), this)) +{ + m_delete->setPos(ITEM_BUTTON_LEFT, getCenterVerticalPos(m_delete)); + m_delete->setVisible(deleteVisible); + m_delete->setOpacity(deleteVisible ? 1.0 : 0.0); + m_check->setPos(ITEM_CHECK_LEFT, getCenterVerticalPos(m_check)); + m_check->hide(); + m_text->setPos(TEXT_LEFT, getCenterVerticalPos(m_text)); + m_text->setCacheMode(ItemCoordinateCache); + + connect(m_delete, SIGNAL(clicked()), this, SLOT(removeFromList())); +} + +void CityListItem::select(bool selected) +{ + m_background->setPixmap(selected ? SELECTED_ITEM_BACKGROUND : ITEM_BACKGROUND); + m_check->setVisible(selected); +} + +void CityListItem::removeFromList() +{ + if (!m_list->busy()) + m_list->removeItem(this); +} + +qreal CityListItem::contentHeight() const +{ + return m_height; +} + +QAbstractAnimation *CityListItem::getButtonAnimation(bool hide) +{ + return getFadeAnimation(m_delete, hide, 150); +} + +QAbstractAnimation *CityListItem::getShowAnimation() +{ + return getFadeAnimation(this, false, 150); +} + +QAbstractAnimation *CityListItem::getHideAnimation() +{ + return getFadeAnimation(this, true, 150); +} + +template<class T>QAbstractAnimation *CityListItem::getFadeAnimation(T *target, bool hide, int msecs) +{ + target->setOpacity(hide ? 1.0 : 0.0); + target->show(); + QPropertyAnimation* lResult = new QPropertyAnimation(target, "opacity"); + lResult->setEasingCurve(QEasingCurve::OutExpo); + lResult->setStartValue(hide ? 1.0 : 0.0); + lResult->setEndValue(hide ? 0.0 : 1.0); + lResult->setDuration(msecs); + if (hide) + connect(lResult, SIGNAL(finished()), target, SLOT(doHide())); + return lResult; +} + +// CityContentList + +CityContentList::CityContentList(QList<ForecastData> &contentList, QObject *holder) + : ContentList(0) + , m_holder(holder) + , m_contentList(contentList) +{ + QList<ContentListItem*> items; + foreach (const ForecastData &data, m_contentList) + items.append(new CityListItem(data, this, m_contentList.count() > minListCount)); + appendItems(items); +} + +void CityContentList::addForecast(const ForecastData &data) +{ + m_contentList.append(data); + addItem(new CityListItem(data, this, m_contentList.count() > minListCount)); +} + +void CityContentList::select(const QString &selected) +{ + for (int i = 0; i < itemCount(); ++i) { + CityListItem *item = static_cast<CityListItem*>(getItem(i)); + if (item) + item->select(item->data().key() == selected); + } +} + +QAbstractAnimation *CityContentList::getInsertAnimation(int idx, qreal height) +{ + if (idx < 0 || idx > itemCount()) + return 0; + + QList<QAbstractAnimation*> list; + + if (m_holder && itemCount() < maxVisibleItems) + list.append(getMoveAnimation(m_holder, -height)); + + if (idx < itemCount()) { + for (int i = idx; i < itemCount(); ++i) + list.append(getItemMoveAnimation(i, height)); + } + + if (parentItem() && itemCount() >= maxVisibleItems) { + qreal top = parentItem()->boundingRect().height() - boundingRect().height() - height; + list.append(getMoveAnimation(this, top - pos().y())); + + } + + if (itemCount() == minListCount) { + for (int i = 0; i < minListCount; ++i) { + CityListItem *item = static_cast<CityListItem*>(getItem(i)); + if (item) + list.append(item->getButtonAnimation(false)); + } + } + + return createCompoundAnimation(list); +} + +QAbstractAnimation *CityContentList::getRemoveAnimation(int idx) +{ + if (idx < 0 || idx >= itemCount()) + return 0; + + qreal offset = -getItem(idx)->contentHeight(); + QList<QAbstractAnimation*> list; + + if (m_holder && itemCount() <= maxVisibleItems) + list.append(getMoveAnimation(m_holder, -offset)); + + for (int i = idx + 1; i < itemCount(); ++i) + list.append(getItemMoveAnimation(i, offset)); + + qreal newHeight = boundingRect().height() - getItem(idx)->contentHeight(); + + if (parentItem()) { + if (newHeight < parentItem()->boundingRect().height()) { + if (pos().y() != 0) + list.append(getMoveAnimation(this, -pos().y())); + } else { + if (pos().y() + newHeight < parentItem()->boundingRect().bottom()) { + qreal offset = parentItem()->boundingRect().bottom() - pos().y() - newHeight; + list.append(getMoveAnimation(this, offset)); + } + } + } + + if (itemCount() == minListCount + 1) { + for (int i = 0; i <= minListCount; ++i) { + if (i != idx) { + CityListItem *item = static_cast<CityListItem*>(getItem(i)); + if (item) + list.append(item->getButtonAnimation(true)); + + } + } + } + + m_contentList.removeAt(idx); + + + return createCompoundAnimation(list); +} + +QAbstractAnimation *CityContentList::createCompoundAnimation(QList<QAbstractAnimation*> list) +{ + if (list.count() == 0) + return 0; + if (list.count() == 1) + return list[0]; + QParallelAnimationGroup *result = new QParallelAnimationGroup(); + for (int i = 0; i < list.count(); ++i) + result->addAnimation(list[i]); + return result; + +} + +QAbstractAnimation *CityContentList::getMoveAnimation(QObject *object, qreal offset) +{ + if (!object) + return 0; + qreal itemTop = object->property(ITEM_TOP_PROPERTY_NAME).toReal(); + QPropertyAnimation* lResult = new QPropertyAnimation(object, ITEM_TOP_PROPERTY_NAME); + + lResult->setEasingCurve(QEasingCurve::OutExpo); + lResult->setStartValue(itemTop); + lResult->setEndValue(itemTop + offset); + lResult->setDuration(150); + return lResult; +} + +QAbstractAnimation *CityContentList::getItemMoveAnimation(int idx, qreal offset) +{ + return getMoveAnimation(getItem(idx), offset); +} + +// CityList + +CityList::CityList(QList<ForecastData> &contentList, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_itemHeight(ITEM_BACKGROUND.height()) + , m_topHeight(LIST_TOP_PIXMAP.height()) + , m_boundingRect(getBoundingRect()) + , m_paintRect(getPaintRect()) + , m_top(new QGraphicsPixmapItem(LIST_TOP_PIXMAP, this)) + , m_list(new CityContentList(contentList, this)) + , m_scrollBox(new CityListScrollBox(m_list, this)) +{ + m_top->setPos(-m_top->boundingRect().left(), -m_top->boundingRect().top()); + m_scrollBox->setRect(m_paintRect); + m_list->setWidth(m_paintRect.width()); +} + +qreal CityList::initialTop() const +{ + int count = maxVisibleItems - m_list->itemCount(); + if (count <= 0) + return 0; + return count * m_itemHeight; +} + +QRectF CityList::getBoundingRect() +{ + QRectF result(QPointF(0.0, 0.0), Settings::windowSize()); + result.setHeight(m_topHeight + maxVisibleItems * m_itemHeight); + result.moveTo(0.0, 0.0); + return result; +} + +QRectF CityList::getPaintRect() +{ + QRectF result(QPointF(0.0, 0.0), Settings::windowSize()); + result.setHeight(maxVisibleItems * m_itemHeight); + result.moveTo(0.0, m_topHeight); + return result; +} + +int CityList::loadImages() +{ + PixmapLoader::load("list_top"); + PixmapLoader::load("list_item_bg"); + PixmapLoader::load("list_item_selected_bg"); + PixmapLoader::load("button_list_delete"); + PixmapLoader::load("list_check"); + return 5; +} + +void CityList::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event); +} + +QRectF CityList::boundingRect () const +{ + return m_boundingRect; +} + +void CityList::paint(QPainter *painter, + const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + painter->fillRect(m_paintRect, QColor(7, 18, 23, 255)); +} |