From 25ba469ae5a2df8fbe75beb8b66fb12c3fe1dcb2 Mon Sep 17 00:00:00 2001 From: Ariya Hidayat Date: Wed, 19 Aug 2009 14:05:40 +0200 Subject: Import the flick list (kinetic scrolling) example. Originally it was published in Qt Labs: http://labs.trolltech.com/blogs/2009/07/19/kinetic-scrolling-on-any-widgets/ Reviewed-by: Jason Barron --- demos/embedded/embedded.pro | 2 +- demos/embedded/flickable/flickable.cpp | 284 +++++++++++++++++++++ demos/embedded/flickable/flickable.h | 80 ++++++ demos/embedded/flickable/flickable.pro | 2 + demos/embedded/flickable/main.cpp | 233 +++++++++++++++++ demos/embedded/fluidlauncher/config_s60/config.xml | 1 + .../fluidlauncher/screenshots/flickable.png | Bin 0 -> 21107 bytes 7 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 demos/embedded/flickable/flickable.cpp create mode 100644 demos/embedded/flickable/flickable.h create mode 100644 demos/embedded/flickable/flickable.pro create mode 100644 demos/embedded/flickable/main.cpp create mode 100644 demos/embedded/fluidlauncher/screenshots/flickable.png diff --git a/demos/embedded/embedded.pro b/demos/embedded/embedded.pro index e425eb5..7fcae45 100644 --- a/demos/embedded/embedded.pro +++ b/demos/embedded/embedded.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -SUBDIRS = styledemo raycasting +SUBDIRS = styledemo raycasting flickable contains(QT_CONFIG, svg) { SUBDIRS += embeddedsvgviewer \ diff --git a/demos/embedded/flickable/flickable.cpp b/demos/embedded/flickable/flickable.cpp new file mode 100644 index 0000000..8fbefe6 --- /dev/null +++ b/demos/embedded/flickable/flickable.cpp @@ -0,0 +1,284 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "flickable.h" + +#include +#include + +class FlickableTicker: QObject +{ +public: + FlickableTicker(Flickable *scroller) { + m_scroller = scroller; + } + + void start(int interval) { + if (!m_timer.isActive()) + m_timer.start(interval, this); + } + + void stop() { + m_timer.stop(); + } + +protected: + void timerEvent(QTimerEvent *event) { + Q_UNUSED(event); + m_scroller->tick(); + } + +private: + Flickable *m_scroller; + QBasicTimer m_timer; +}; + +class FlickablePrivate +{ +public: + typedef enum { + Steady, + Pressed, + ManualScroll, + AutoScroll, + Stop + } State; + + State state; + int threshold; + QPoint pressPos; + QPoint offset; + QPoint delta; + QPoint speed; + FlickableTicker *ticker; + QTime timeStamp; + QWidget *target; + QList ignoreList; +}; + +Flickable::Flickable() +{ + d = new FlickablePrivate; + d->state = FlickablePrivate::Steady; + d->threshold = 10; + d->ticker = new FlickableTicker(this); + d->timeStamp = QTime::currentTime(); + d->target = 0; +} + +Flickable::~Flickable() +{ + delete d; +} + +void Flickable::setThreshold(int th) +{ + if (th >= 0) + d->threshold = th; +} + +int Flickable::threshold() const +{ + return d->threshold; +} + +void Flickable::setAcceptMouseClick(QWidget *target) +{ + d->target = target; +} + +static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64) +{ + int x = qBound(-max, speed.x(), max); + int y = qBound(-max, speed.y(), max); + x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a); + y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a); + return QPoint(x, y); +} + +void Flickable::handleMousePress(QMouseEvent *event) +{ + event->ignore(); + + if (event->button() != Qt::LeftButton) + return; + + if (d->ignoreList.removeAll(event)) + return; + + switch (d->state) { + + case FlickablePrivate::Steady: + event->accept(); + d->state = FlickablePrivate::Pressed; + d->pressPos = event->pos(); + break; + + case FlickablePrivate::AutoScroll: + event->accept(); + d->state = FlickablePrivate::Stop; + d->speed = QPoint(0, 0); + d->pressPos = event->pos(); + d->offset = scrollOffset(); + d->ticker->stop(); + break; + + default: + break; + } +} + +void Flickable::handleMouseRelease(QMouseEvent *event) +{ + event->ignore(); + + if (event->button() != Qt::LeftButton) + return; + + if (d->ignoreList.removeAll(event)) + return; + + QPoint delta; + + switch (d->state) { + + case FlickablePrivate::Pressed: + event->accept(); + d->state = FlickablePrivate::Steady; + if (d->target) { + QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress, + d->pressPos, Qt::LeftButton, + Qt::LeftButton, Qt::NoModifier); + QMouseEvent *event2 = new QMouseEvent(*event); + d->ignoreList << event1; + d->ignoreList << event2; + QApplication::postEvent(d->target, event1); + QApplication::postEvent(d->target, event2); + } + break; + + case FlickablePrivate::ManualScroll: + event->accept(); + delta = event->pos() - d->pressPos; + if (d->timeStamp.elapsed() > 100) { + d->timeStamp = QTime::currentTime(); + d->speed = delta - d->delta; + d->delta = delta; + } + d->offset = scrollOffset(); + d->pressPos = event->pos(); + if (d->speed == QPoint(0, 0)) { + d->state = FlickablePrivate::Steady; + } else { + d->speed /= 4; + d->state = FlickablePrivate::AutoScroll; + d->ticker->start(20); + } + break; + + case FlickablePrivate::Stop: + event->accept(); + d->state = FlickablePrivate::Steady; + d->offset = scrollOffset(); + break; + + default: + break; + } +} + +void Flickable::handleMouseMove(QMouseEvent *event) +{ + event->ignore(); + + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (d->ignoreList.removeAll(event)) + return; + + QPoint delta; + + switch (d->state) { + + case FlickablePrivate::Pressed: + case FlickablePrivate::Stop: + delta = event->pos() - d->pressPos; + if (delta.x() > d->threshold || delta.x() < -d->threshold || + delta.y() > d->threshold || delta.y() < -d->threshold) { + d->timeStamp = QTime::currentTime(); + d->state = FlickablePrivate::ManualScroll; + d->delta = QPoint(0, 0); + d->pressPos = event->pos(); + event->accept(); + } + break; + + case FlickablePrivate::ManualScroll: + event->accept(); + delta = event->pos() - d->pressPos; + setScrollOffset(d->offset - delta); + if (d->timeStamp.elapsed() > 100) { + d->timeStamp = QTime::currentTime(); + d->speed = delta - d->delta; + d->delta = delta; + } + break; + + default: + break; + } +} + +void Flickable::tick() +{ + if (d->state == FlickablePrivate:: AutoScroll) { + d->speed = deaccelerate(d->speed); + setScrollOffset(d->offset - d->speed); + d->offset = scrollOffset(); + if (d->speed == QPoint(0, 0)) { + d->state = FlickablePrivate::Steady; + d->ticker->stop(); + } + } else { + d->ticker->stop(); + } +} diff --git a/demos/embedded/flickable/flickable.h b/demos/embedded/flickable/flickable.h new file mode 100644 index 0000000..26497db --- /dev/null +++ b/demos/embedded/flickable/flickable.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FLICKABLE_H +#define FLICKABLE_H + +class QMouseEvent; +class QPoint; +class QWidget; + +class FlickableTicker; +class FlickablePrivate; + +class Flickable +{ +public: + + Flickable(); + virtual ~Flickable(); + + void setThreshold(int threshold); + int threshold() const; + + void setAcceptMouseClick(QWidget *target); + + void handleMousePress(QMouseEvent *event); + void handleMouseMove(QMouseEvent *event); + void handleMouseRelease(QMouseEvent *event); + +protected: + virtual QPoint scrollOffset() const = 0; + virtual void setScrollOffset(const QPoint &offset) = 0; + +private: + void tick(); + +private: + FlickablePrivate *d; + friend class FlickableTicker; +}; + +#endif // FLICKABLE_H diff --git a/demos/embedded/flickable/flickable.pro b/demos/embedded/flickable/flickable.pro new file mode 100644 index 0000000..3c021dd --- /dev/null +++ b/demos/embedded/flickable/flickable.pro @@ -0,0 +1,2 @@ +SOURCES = flickable.cpp main.cpp +HEADERS = flickable.h diff --git a/demos/embedded/flickable/main.cpp b/demos/embedded/flickable/main.cpp new file mode 100644 index 0000000..cc024d9 --- /dev/null +++ b/demos/embedded/flickable/main.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "flickable.h" + +// Returns a list of two-word color names +static QStringList colorPairs(int max) +{ + // capitalize the first letter + QStringList colors = QColor::colorNames(); + colors.removeAll("transparent"); + int num = colors.count(); + for (int c = 0; c < num; ++c) + colors[c] = colors[c][0].toUpper() + colors[c].mid(1); + + // combine two colors, e.g. "lime skyblue" + QStringList combinedColors; + for (int i = 0; i < num; ++i) + for (int j = 0; j < num; ++j) + combinedColors << QString("%1 %2").arg(colors[i]).arg(colors[j]); + + // randomize it + colors.clear(); + while (combinedColors.count()) { + int i = qrand() % combinedColors.count(); + colors << combinedColors[i]; + combinedColors.removeAt(i); + if (colors.count() == max) + break; + } + + return colors; +} + +class ColorList : public QWidget, public Flickable +{ + Q_OBJECT + +public: + ColorList(QWidget *parent = 0) + : QWidget(parent) { + m_offset = 0; + m_height = QFontMetrics(font()).height() + 5; + m_highlight = -1; + m_selected = -1; + + QStringList colors = colorPairs(999); + for (int i = 0; i < colors.count(); ++i) { + QString c = colors[i]; + QString str; + str.sprintf("%4d", i + 1); + m_colorNames << (str + " " + c); + + QStringList duet = c.split(' '); + m_firstColor << duet[0]; + m_secondColor << duet[1]; + } + + setAttribute(Qt::WA_OpaquePaintEvent, true); + setAttribute(Qt::WA_NoSystemBackground, true); + + setMouseTracking(true); + Flickable::setAcceptMouseClick(this); + } + +protected: + // reimplement from Flickable + virtual QPoint scrollOffset() const { + return QPoint(0, m_offset); + } + + // reimplement from Flickable + virtual void setScrollOffset(const QPoint &offset) { + int yy = offset.y(); + if (yy != m_offset) { + m_offset = qBound(0, yy, m_height * m_colorNames.count() - height()); + update(); + } + } + +protected: + void paintEvent(QPaintEvent *event) { + QPainter p(this); + p.fillRect(event->rect(), Qt::white); + int start = m_offset / m_height; + int y = start * m_height - m_offset; + if (m_offset <= 0) { + start = 0; + y = -m_offset; + } + int end = start + height() / m_height + 1; + if (end > m_colorNames.count() - 1) + end = m_colorNames.count() - 1; + for (int i = start; i <= end; ++i, y += m_height) { + + p.setBrush(Qt::NoBrush); + p.setPen(Qt::black); + if (i == m_highlight) { + p.fillRect(0, y, width(), m_height, QColor(0, 64, 128)); + p.setPen(Qt::white); + } + if (i == m_selected) { + p.fillRect(0, y, width(), m_height, QColor(0, 128, 240)); + p.setPen(Qt::white); + } + + p.drawText(m_height + 2, y, width(), m_height, Qt::AlignVCenter, m_colorNames[i]); + + p.setPen(Qt::NoPen); + p.setBrush(m_firstColor[i]); + p.drawRect(1, y + 1, m_height - 2, m_height - 2); + p.setBrush(m_secondColor[i]); + p.drawRect(5, y + 5, m_height - 11, m_height - 11); + } + p.end(); + } + + void keyReleaseEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Down) { + m_offset += 20; + event->accept(); + update(); + return; + } + if (event->key() == Qt::Key_Up) { + m_offset -= 20; + event->accept(); + update(); + return; + } + } + + void mousePressEvent(QMouseEvent *event) { + Flickable::handleMousePress(event); + if (event->isAccepted()) + return; + + if (event->button() == Qt::LeftButton) { + int y = event->pos().y() + m_offset; + int i = y / m_height; + if (i != m_highlight) { + m_highlight = i; + m_selected = -1; + update(); + } + event->accept(); + } + } + + void mouseMoveEvent(QMouseEvent *event) { + Flickable::handleMouseMove(event); + } + + void mouseReleaseEvent(QMouseEvent *event) { + Flickable::handleMouseRelease(event); + if (event->isAccepted()) + return; + + if (event->button() == Qt::LeftButton) { + m_selected = m_highlight; + event->accept(); + update(); + } + } + +private: + int m_offset; + int m_height; + int m_highlight; + int m_selected; + QStringList m_colorNames; + QList m_firstColor; + QList m_secondColor; +}; + +#include "main.moc" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + ColorList list; + list.setWindowTitle("Kinetic Scrolling"); +#ifdef Q_OS_SYMBIAN + list.showMaximized(); +#else + list.resize(320, 320); + list.show(); +#endif + + return app.exec(); +} diff --git a/demos/embedded/fluidlauncher/config_s60/config.xml b/demos/embedded/fluidlauncher/config_s60/config.xml index 08cdb90..dfa8da7 100644 --- a/demos/embedded/fluidlauncher/config_s60/config.xml +++ b/demos/embedded/fluidlauncher/config_s60/config.xml @@ -18,6 +18,7 @@ + diff --git a/demos/embedded/fluidlauncher/screenshots/flickable.png b/demos/embedded/fluidlauncher/screenshots/flickable.png new file mode 100644 index 0000000..7080fc1 Binary files /dev/null and b/demos/embedded/fluidlauncher/screenshots/flickable.png differ -- cgit v0.12