From 886bdd0fa43a2fcdeca306648b643b623af99f88 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Tue, 9 Feb 2016 17:38:28 -0200 Subject: Added TransitionControl and plot rewinding. New component overlays main view and handles middle clicks/drags to performing rewinding. Also sports smooth transitioning back to current projection whenever the mouse button is lifted. Next up, the same kind of transitions in the displayed values. --- main.cpp | 7 ++++++ main_view.qml | 9 ++++++++ manipulationhandler.cpp | 31 ++++++++++++++++++++++--- manipulationhandler.h | 3 ++- pm.pro | 5 ++++ rewindworkerthread.cpp | 23 +++++++++++++++++++ rewindworkerthread.h | 21 +++++++++++++++++ transitioncontrol.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ transitioncontrol.h | 41 +++++++++++++++++++++++++++++++++ 9 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 rewindworkerthread.cpp create mode 100644 rewindworkerthread.h create mode 100644 transitioncontrol.cpp create mode 100644 transitioncontrol.h diff --git a/main.cpp b/main.cpp index 47bea3d..b1a8e8b 100644 --- a/main.cpp +++ b/main.cpp @@ -16,6 +16,7 @@ #include "voronoisplat.h" #include "barchart.h" #include "colormap.h" +#include "transitioncontrol.h" #include "manipulationhandler.h" #include "mapscalehandler.h" #include "selectionhandler.h" @@ -142,6 +143,7 @@ int main(int argc, char **argv) qmlRegisterType("PM", 1, 0, "BarChart"); qmlRegisterType("PM", 1, 0, "VoronoiSplat"); qmlRegisterType("PM", 1, 0, "Colormap"); + qmlRegisterType("PM", 1, 0, "TransitionControl"); qmlRegisterSingletonType
("PM", 1, 0, "Main", mainProvider); QQmlApplicationEngine engine(QUrl("qrc:///main_view.qml")); @@ -153,6 +155,7 @@ int main(int argc, char **argv) m->splat = engine.rootObjects()[0]->findChild("splat"); m->cpBarChart = engine.rootObjects()[0]->findChild("cpBarChart"); m->rpBarChart = engine.rootObjects()[0]->findChild("rpBarChart"); + TransitionControl *plotTC = engine.rootObjects()[0]->findChild("plotTC"); // Keep track of the current cp (in order to save them later, if requested) QObject::connect(m->cpPlot, SIGNAL(xyChanged(const arma::mat &)), @@ -171,6 +174,8 @@ int main(int argc, char **argv) m->rpPlot, SLOT(setXY(const arma::mat &))); QObject::connect(&manipulationHandler, SIGNAL(rpChanged(const arma::mat &)), m->splat, SLOT(setSites(const arma::mat &))); + QObject::connect(plotTC, SIGNAL(tChanged(double)), + &manipulationHandler, SLOT(setRewind(double))); // Keep both scatterplots and the splat scaled equally and relative to the // full plot @@ -246,10 +251,12 @@ int main(int argc, char **argv) &projectionObserver, SLOT(setRPSelection(const std::vector &))); // General component set up + plotTC->setAcceptedMouseButtons(Qt::MiddleButton); m->cpPlot->setDragEnabled(true); m->cpPlot->setAutoScale(false); m->rpPlot->setGlyphSize(3.0f); m->rpPlot->setAutoScale(false); + m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); m->setSelectCPs(); m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); diff --git a/main_view.qml b/main_view.qml index bcf64a5..cfbea4f 100644 --- a/main_view.qml +++ b/main_view.qml @@ -109,6 +109,15 @@ ApplicationWindow { color: "transparent" } } + + TransitionControl { + id: plotTC + objectName: "plotTC" + x: parent.x + y: parent.y + z: 3 + anchors.fill: parent + } } Rectangle { diff --git a/manipulationhandler.cpp b/manipulationhandler.cpp index b298860..7d3e3c0 100644 --- a/manipulationhandler.cpp +++ b/manipulationhandler.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "mp.h" #include "numericrange.h" @@ -30,24 +32,47 @@ void ManipulationHandler::setTechnique(ManipulationHandler::Technique technique) void ManipulationHandler::setCP(const arma::mat &Ys) { + m_prevY = m_Y; + switch (m_technique) { case TECHNIQUE_PLMP: - mp::plmp(m_X, m_cpIndices, Ys, m_Y); + // TODO? + // mp::plmp(m_X, m_cpIndices, Ys, m_Y); break; case TECHNIQUE_LSP: - // TODO + // TODO? // mp::lsp(m_X, m_cpIndices, Ys, m_Y); break; case TECHNIQUE_LAMP: mp::lamp(m_X, m_cpIndices, Ys, m_Y); break; case TECHNIQUE_PEKALSKA: - // TODO + // TODO? // mp::pekalska(m_X, m_cpIndices, Ys, m_Y); break; } + if (m_firstY.n_rows != m_Y.n_rows) { + m_firstY = m_Y; + } + emit cpChanged(m_Y.rows(m_cpIndices)); emit rpChanged(m_Y.rows(m_rpIndices)); emit mapChanged(m_Y); } + +void ManipulationHandler::setRewind(double t) +{ + if (m_prevY.n_rows != m_Y.n_rows) { + return; + } + + arma::mat Y = m_Y * t + m_prevY * (1.0 - t); + emit cpChanged(Y.rows(m_cpIndices)); + emit rpChanged(Y.rows(m_rpIndices)); + + // NOTE: this signal was supposed to be emitted, but since we don't want + // anything besides graphical objects to know the projection is being + // rewound, this is (for now) left out. + // emit mapChanged(Y); +} diff --git a/manipulationhandler.h b/manipulationhandler.h index 78ca1c9..1e17446 100644 --- a/manipulationhandler.h +++ b/manipulationhandler.h @@ -27,9 +27,10 @@ signals: public slots: void setCP(const arma::mat &Ys); + void setRewind(double t); private: - arma::mat m_X, m_Y; + arma::mat m_X, m_Y, m_prevY, m_firstY; arma::uvec m_cpIndices, m_rpIndices; Technique m_technique; }; diff --git a/pm.pro b/pm.pro index e38d3a9..a53e7e1 100644 --- a/pm.pro +++ b/pm.pro @@ -28,6 +28,8 @@ HEADERS += main.h \ colormap.h \ historygraph.h \ barchart.h \ + transitioncontrol.h \ + rewindworkerthread.h \ manipulationhandler.h \ mapscalehandler.h \ numericrange.h \ @@ -37,6 +39,7 @@ HEADERS += main.h \ skelft.h \ skelftkernel.h \ mp.h + SOURCES += main.cpp \ colorscale.cpp \ continuouscolorscale.cpp \ @@ -46,6 +49,8 @@ SOURCES += main.cpp \ colormap.cpp \ historygraph.cpp \ barchart.cpp \ + transitioncontrol.cpp \ + rewindworkerthread.cpp \ manipulationhandler.cpp \ mapscalehandler.cpp \ selectionhandler.cpp \ diff --git a/rewindworkerthread.cpp b/rewindworkerthread.cpp new file mode 100644 index 0000000..f9fa999 --- /dev/null +++ b/rewindworkerthread.cpp @@ -0,0 +1,23 @@ +#include "rewindworkerthread.h" + +// The full duration (usecs) of the restoration animation +static const double DURATION = 250000; + +// The time to wait (usecs) before the next animation tick +static const double TICK_TIME = DURATION / 60.0; + +// The amount to increase 't' per time step +static const double TICK_SIZE = TICK_TIME / DURATION; + +void RewindWorkerThread::run() +{ + double t = m_control->t(); + + while (t + TICK_SIZE < 1.0) { + t += TICK_SIZE; + m_control->setT(t); + QThread::usleep(TICK_TIME); + } + + m_control->setT(1.0); +} diff --git a/rewindworkerthread.h b/rewindworkerthread.h new file mode 100644 index 0000000..0bbaeb6 --- /dev/null +++ b/rewindworkerthread.h @@ -0,0 +1,21 @@ +#ifndef REWINDWORKERTHREAD_H +#define REWINDWORKERTHREAD_H + +#include "transitioncontrol.h" + +#include +#include + +class RewindWorkerThread + : public QThread +{ + Q_OBJECT +public: + RewindWorkerThread(TransitionControl *control) { m_control = control; } + void run(); + +private: + TransitionControl *m_control; +}; + +#endif // REWINDWORKERTHREAD_H diff --git a/transitioncontrol.cpp b/transitioncontrol.cpp new file mode 100644 index 0000000..cf8f644 --- /dev/null +++ b/transitioncontrol.cpp @@ -0,0 +1,61 @@ +#include "transitioncontrol.h" + +#include +#include + +#include "rewindworkerthread.h" + +// The mouse button used for interaction +static const Qt::MouseButton MOUSE_BUTTON = Qt::MiddleButton; + +TransitionControl::TransitionControl() + : m_t(1.0) + , m_startPos(-1) +{ +} + +void TransitionControl::setT(double t) +{ + if (t < 0.0 || t > 1.0) { + return; + } + + m_t = t; + emit tChanged(m_t); +} + +void TransitionControl::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != MOUSE_BUTTON) { + return; + } + + m_t = 1.0; + m_startPos = event->pos().x(); +} + +void TransitionControl::mouseMoveEvent(QMouseEvent *event) +{ + int x = event->pos().x(); + if (!(event->buttons() & MOUSE_BUTTON) || x > m_startPos || x < 0) { + return; + } + + m_t = double(x) / m_startPos; + emit tChanged(m_t); +} + +void TransitionControl::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != MOUSE_BUTTON) { + return; + } + + // Back to initial state + m_startPos = -1; + + // We now have to smoothly go back to m_t == 1.0 + m_rewindThread = new RewindWorkerThread(this); + connect(m_rewindThread, &QThread::finished, m_rewindThread, &QObject::deleteLater); + m_rewindThread->start(); +} diff --git a/transitioncontrol.h b/transitioncontrol.h new file mode 100644 index 0000000..ac84786 --- /dev/null +++ b/transitioncontrol.h @@ -0,0 +1,41 @@ +#ifndef TRANSITIONCONTROL_H +#define TRANSITIONCONTROL_H + +#include + +/* + * This component emits signals indicating how far from its left edge is the + * mouse since the mouse button was pressed (starting from t == 1.0 with 0.0 + * being exactly at the left edge). As the mouse is released, it emits periodic + * signals incrementing the value until it is restored to the default. + */ +class TransitionControl : + public QQuickItem +{ + Q_OBJECT +public: + TransitionControl(); + double t() const { return m_t; } + +signals: + void tChanged(double t) const; + +public slots: + void setT(double t); + +protected: + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + +private: + double m_t; + + // The x pos where interaction started + int m_startPos; + + // Controls the smooth rewind transition + QThread *m_rewindThread; +}; + +#endif // TRANSITIONCONTROL_H -- cgit v1.2.3