From d0c26f42d76060ce76b22f68d5b77758d124586e Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Thu, 6 Jul 2023 16:08:19 +0200 Subject: [PATCH] profile: implement animation of the tooltip item To do so, generalize the animation routine. This seems to expose a QtQuick bug: we get spurious hover-events when the tooltip item is updated in the animation. We have to check for that to prevent en endless loop (until the user moves the mouse out of the profile window). Signed-off-by: Berthold Stoeger --- profile-widget/profileview.cpp | 66 +++++++++++++++++++++++++--------- profile-widget/profileview.h | 5 ++- profile-widget/tooltipitem.cpp | 34 ++++++++++-------- profile-widget/tooltipitem.h | 4 +-- qt-quick/chartitem.cpp | 31 ++++++++++++++++ qt-quick/chartitem.h | 14 ++++++++ 6 files changed, 120 insertions(+), 34 deletions(-) diff --git a/profile-widget/profileview.cpp b/profile-widget/profileview.cpp index 5ea1cdb30..576d7d9d4 100644 --- a/profile-widget/profileview.cpp +++ b/profile-widget/profileview.cpp @@ -17,33 +17,52 @@ #include #include -// Class for animations (if any). Might want to do our own. +// Class templates for animations (if any). Might want to do our own. +// Calls the function object passed in the constructor with a time argument, +// where 0.0 = start at 1.0 = end.. +// On the last invocation, a 1.0 literal is passed, so floating-point +// comparison is OK. class ProfileAnimation : public QAbstractAnimation { - ProfileView &view; - // For historical reasons, speed is actually the duration - // (i.e. the reciprocal of speed). Ouch, that hurts. - int speed; - int duration() const override { return speed; } +protected: + // For historical reasons, speed is actually the duration + // (i.e. the reciprocal of speed). Ouch, that hurts. + int speed; +public: + ProfileAnimation(int animSpeed) : speed(animSpeed) + { + } +}; + +template +class ProfileAnimationTemplate : public ProfileAnimation { void updateCurrentTime(int time) override { // Note: we explicitly pass 1.0 at the end, so that // the callee can do a simple float comparison for "end". - view.anim(time == speed ? 1.0 - : static_cast(time) / speed); + func(time == speed ? 1.0 + : static_cast(time) / speed); } + FUNC func; public: - ProfileAnimation(ProfileView &view, int animSpeed) : - view(view), - speed(animSpeed) + ProfileAnimationTemplate(FUNC func, int animSpeed) : + ProfileAnimation(animSpeed), + func(func) { start(); } }; +// Helper function to make creation of animations somewhat more palatable +template +std::unique_ptr> make_anim(FUNC func, int animSpeed) +{ + return std::make_unique>(func, animSpeed); +} + ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count), d(nullptr), dc(0), @@ -201,7 +220,7 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) if (prefs.infobox) { QPoint pos = mapFromGlobal(QCursor::pos()).toPoint(); tooltip->setVisible(true); - updateTooltip(pos, flags & RenderFlags::PlanMode); + updateTooltip(pos, flags & RenderFlags::PlanMode, animSpeed); } else { tooltip->setVisible(false); } @@ -210,7 +229,7 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) if (animSpeed <= 0) animation.reset(); else - animation = std::make_unique(*this, animSpeed); + animation = make_anim([this](double progress) { anim(progress); }, animSpeed); } void ProfileView::anim(double fraction) @@ -375,15 +394,30 @@ void ProfileView::hoverMoveEvent(QHoverEvent *event) { if (!profileScene) return; + + // This is incredibly stupid: For some weird reason (a bug?), when + // resizing the ToolTipItem we get spurious hoverMoveEvents, which + // restarts the animation, giving an infinite loop. + // Prevent this by comparing to the old mouse position. + if (std::exchange(previousHoveMovePosition, event->pos()) == previousHoveMovePosition) + return; + if (tooltip && prefs.infobox) { - updateTooltip(event->pos(), false); // TODO: plan mode + updateTooltip(event->pos(), false, qPrefDisplay::animation_speed()); // TODO: plan mode update(); } } -void ProfileView::updateTooltip(QPointF pos, bool plannerMode) +void ProfileView::updateTooltip(QPointF pos, bool plannerMode, int animSpeed) { int time = profileScene->timeAt(pos); auto events = profileScene->eventsAt(pos); - tooltip->update(d, dpr, time, profileScene->getPlotInfo(), events, plannerMode); + tooltip->update(d, dpr, time, profileScene->getPlotInfo(), events, plannerMode, animSpeed); + + // Reset animation. + if (animSpeed <= 0) + tooltip_animation.reset(); + else + tooltip_animation = make_anim([this](double progress) + { if (tooltip) tooltip->anim(progress); update(); }, animSpeed); } diff --git a/profile-widget/profileview.h b/profile-widget/profileview.h index 8d70a0479..350d5aac1 100644 --- a/profile-widget/profileview.h +++ b/profile-widget/profileview.h @@ -74,7 +74,10 @@ private: void mouseReleaseEvent(QMouseEvent *event) override; ChartItemPtr tooltip; - void updateTooltip(QPointF pos, bool plannerMode); + void updateTooltip(QPointF pos, bool plannerMode, int animSpeed); + std::unique_ptr tooltip_animation; + + QPointF previousHoveMovePosition; // For mobile int getDiveId() const; diff --git a/profile-widget/tooltipitem.cpp b/profile-widget/tooltipitem.cpp index 906c9d572..44317ffaa 100644 --- a/profile-widget/tooltipitem.cpp +++ b/profile-widget/tooltipitem.cpp @@ -36,10 +36,10 @@ static QFont makeFont(double dpr) } ToolTipItem::ToolTipItem(ChartView &view, double dpr) : - ChartRectItem(view, ProfileZValue::ToolTipItem, - QPen(tooltipBorderColor, tooltipBorder), - QBrush(tooltipColor), tooltipBorderRadius, - true), + AnimatedChartRectItem(view, ProfileZValue::ToolTipItem, + QPen(tooltipBorderColor, tooltipBorder), + QBrush(tooltipColor), tooltipBorderRadius, + true), font(makeFont(dpr)), fm(font), fontHeight(fm.height()) @@ -100,7 +100,7 @@ static QPixmap drawTissues(const plot_info &pInfo, double dpr, int idx, bool inP } void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &pInfo, - const std::vector> &events, bool inPlanner) + const std::vector> &events, bool inPlanner, int animSpeed) { auto [idx, lines] = get_plot_details_new(d, pInfo, time); @@ -135,31 +135,35 @@ void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &p height = std::max(height, static_cast(tissues.height())); height += 4.0 * tooltipBorder + title.height(); - ChartRectItem::resize(QSizeF(width, height)); - painter->setFont(font); - painter->setPen(QPen(tooltipFontColor)); // QPainter uses QPen to set text color! + QPixmap pixmap(lrint(width), lrint(height)); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + + painter.setFont(font); + painter.setPen(QPen(tooltipFontColor)); // QPainter uses QPen to set text color! double x = 4.0 * tooltipBorder + tissues.width(); double y = 2.0 * tooltipBorder; double titleOffset = (width - title.width()) / 2.0; - painter->drawPixmap(lrint(titleOffset), lrint(y), title, 0, 0, title.width(), title.height()); + painter.drawPixmap(lrint(titleOffset), lrint(y), title, 0, 0, title.width(), title.height()); y += round(fontHeight); - painter->drawPixmap(lrint(2.0 * tooltipBorder), lrint(y), tissues, 0, 0, tissues.width(), tissues.height()); + painter.drawPixmap(lrint(2.0 * tooltipBorder), lrint(y), tissues, 0, 0, tissues.width(), tissues.height()); y += round(fontHeight); for (auto &[s,w]: strings) { QRectF rect(x, y, w, fontHeight); - painter->drawText(rect, s); + painter.drawText(rect, s); y += fontHeight; } for (size_t i = 0; i < events.size(); ++i) { QSizeF size = event_sizes[i]; auto &[s, pixmap] = events[i]; - painter->drawPixmap(lrint(x), lrint(y + (size.height() - pixmap.height())/2.0), pixmap, - 0, 0, pixmap.width(), pixmap.height()); + painter.drawPixmap(lrint(x), lrint(y + (size.height() - pixmap.height())/2.0), pixmap, + 0, 0, pixmap.width(), pixmap.height()); QRectF rect(x + pixmap.width(), round(y + (size.height() - fontHeight) / 2.0), size.width(), fontHeight); - painter->drawText(rect, s); + painter.drawText(rect, s); y += size.height(); } - setTextureDirty(); + + AnimatedChartRectItem::setPixmap(pixmap, animSpeed); } void ToolTipItem::stopDrag(QPointF pos) diff --git a/profile-widget/tooltipitem.h b/profile-widget/tooltipitem.h index 5e58529eb..2eac448f4 100644 --- a/profile-widget/tooltipitem.h +++ b/profile-widget/tooltipitem.h @@ -10,11 +10,11 @@ struct dive; struct plot_info; -class ToolTipItem : public ChartRectItem { +class ToolTipItem : public AnimatedChartRectItem { public: ToolTipItem(ChartView &view, double dpr); void update(const dive *d, double dpr, int time, const plot_info &pInfo, - const std::vector> &events, bool inPlanner); + const std::vector> &events, bool inPlanner, int animSpeed); private: QFont font; QFontMetrics fm; diff --git a/qt-quick/chartitem.cpp b/qt-quick/chartitem.cpp index c5e63ede9..736d5ab42 100644 --- a/qt-quick/chartitem.cpp +++ b/qt-quick/chartitem.cpp @@ -150,6 +150,37 @@ void ChartRectItem::resize(QSizeF size) painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize); } +AnimatedChartRectItem::~AnimatedChartRectItem() +{ +} + +void AnimatedChartRectItem::setPixmap(const QPixmap &p, int animSpeed) +{ + if (animSpeed <= 0) { + resize(p.size()); + painter->drawPixmap(0, 0, p, 0, 0, p.width(), p.height()); + setTextureDirty(); + return; + } + pixmap = p; + originalSize = img ? img->size() : QSize(1,1); +} + +static int mid(int from, int to, double fraction) +{ + return fraction == 1.0 ? to + : lrint(from + (to - from) * fraction); +} + +void AnimatedChartRectItem::anim(double fraction) +{ + QSize s(mid(originalSize.width(), pixmap.width(), fraction), + mid(originalSize.height(), pixmap.height(), fraction)); + resize(s); + painter->drawPixmap(0, 0, pixmap, 0, 0, s.width(), s.height()); + setTextureDirty(); +} + ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector &text, bool center) : ChartPixmapItem(v, z), f(f), center(center) { diff --git a/qt-quick/chartitem.h b/qt-quick/chartitem.h index 63919c95d..5e0a9866a 100644 --- a/qt-quick/chartitem.h +++ b/qt-quick/chartitem.h @@ -98,6 +98,20 @@ private: double radius; }; +// A ChartRectItem that is animated on size change. +// Same as ChartPixmapItem, but resize is called with a "speed" parameter +// (which is actually a time parameter for historical reasons). +class AnimatedChartRectItem : public ChartRectItem { +public: + using ChartRectItem::ChartRectItem; + ~AnimatedChartRectItem(); + void setPixmap(const QPixmap &pixmap, int animSpeed); + void anim(double progress); +private: + QPixmap pixmap; + QSize originalSize; +}; + // Attention: text is only drawn after calling setColor()! class ChartTextItem : public ChartPixmapItem { public: