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: