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 <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2023-07-06 16:08:19 +02:00
parent 06b0a7eeae
commit d0c26f42d7
6 changed files with 120 additions and 34 deletions

View file

@ -17,33 +17,52 @@
#include <QDebug> #include <QDebug>
#include <QElapsedTimer> #include <QElapsedTimer>
// 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 { 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 int duration() const override
{ {
return speed; 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 <typename FUNC>
class ProfileAnimationTemplate : public ProfileAnimation {
void updateCurrentTime(int time) override void updateCurrentTime(int time) override
{ {
// Note: we explicitly pass 1.0 at the end, so that // Note: we explicitly pass 1.0 at the end, so that
// the callee can do a simple float comparison for "end". // the callee can do a simple float comparison for "end".
view.anim(time == speed ? 1.0 func(time == speed ? 1.0
: static_cast<double>(time) / speed); : static_cast<double>(time) / speed);
} }
FUNC func;
public: public:
ProfileAnimation(ProfileView &view, int animSpeed) : ProfileAnimationTemplate(FUNC func, int animSpeed) :
view(view), ProfileAnimation(animSpeed),
speed(animSpeed) func(func)
{ {
start(); start();
} }
}; };
// Helper function to make creation of animations somewhat more palatable
template <typename FUNC>
std::unique_ptr<ProfileAnimationTemplate<FUNC>> make_anim(FUNC func, int animSpeed)
{
return std::make_unique<ProfileAnimationTemplate<FUNC>>(func, animSpeed);
}
ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count), ProfileView::ProfileView(QQuickItem *parent) : ChartView(parent, ProfileZValue::Count),
d(nullptr), d(nullptr),
dc(0), dc(0),
@ -201,7 +220,7 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
if (prefs.infobox) { if (prefs.infobox) {
QPoint pos = mapFromGlobal(QCursor::pos()).toPoint(); QPoint pos = mapFromGlobal(QCursor::pos()).toPoint();
tooltip->setVisible(true); tooltip->setVisible(true);
updateTooltip(pos, flags & RenderFlags::PlanMode); updateTooltip(pos, flags & RenderFlags::PlanMode, animSpeed);
} else { } else {
tooltip->setVisible(false); tooltip->setVisible(false);
} }
@ -210,7 +229,7 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags)
if (animSpeed <= 0) if (animSpeed <= 0)
animation.reset(); animation.reset();
else else
animation = std::make_unique<ProfileAnimation>(*this, animSpeed); animation = make_anim([this](double progress) { anim(progress); }, animSpeed);
} }
void ProfileView::anim(double fraction) void ProfileView::anim(double fraction)
@ -375,15 +394,30 @@ void ProfileView::hoverMoveEvent(QHoverEvent *event)
{ {
if (!profileScene) if (!profileScene)
return; 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) { if (tooltip && prefs.infobox) {
updateTooltip(event->pos(), false); // TODO: plan mode updateTooltip(event->pos(), false, qPrefDisplay::animation_speed()); // TODO: plan mode
update(); update();
} }
} }
void ProfileView::updateTooltip(QPointF pos, bool plannerMode) void ProfileView::updateTooltip(QPointF pos, bool plannerMode, int animSpeed)
{ {
int time = profileScene->timeAt(pos); int time = profileScene->timeAt(pos);
auto events = profileScene->eventsAt(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);
} }

View file

@ -74,7 +74,10 @@ private:
void mouseReleaseEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override;
ChartItemPtr<ToolTipItem> tooltip; ChartItemPtr<ToolTipItem> tooltip;
void updateTooltip(QPointF pos, bool plannerMode); void updateTooltip(QPointF pos, bool plannerMode, int animSpeed);
std::unique_ptr<ProfileAnimation> tooltip_animation;
QPointF previousHoveMovePosition;
// For mobile // For mobile
int getDiveId() const; int getDiveId() const;

View file

@ -36,10 +36,10 @@ static QFont makeFont(double dpr)
} }
ToolTipItem::ToolTipItem(ChartView &view, double dpr) : ToolTipItem::ToolTipItem(ChartView &view, double dpr) :
ChartRectItem(view, ProfileZValue::ToolTipItem, AnimatedChartRectItem(view, ProfileZValue::ToolTipItem,
QPen(tooltipBorderColor, tooltipBorder), QPen(tooltipBorderColor, tooltipBorder),
QBrush(tooltipColor), tooltipBorderRadius, QBrush(tooltipColor), tooltipBorderRadius,
true), true),
font(makeFont(dpr)), font(makeFont(dpr)),
fm(font), fm(font),
fontHeight(fm.height()) 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, void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &pInfo,
const std::vector<std::pair<QString, QPixmap>> &events, bool inPlanner) const std::vector<std::pair<QString, QPixmap>> &events, bool inPlanner, int animSpeed)
{ {
auto [idx, lines] = get_plot_details_new(d, pInfo, time); 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<double>(tissues.height())); height = std::max(height, static_cast<double>(tissues.height()));
height += 4.0 * tooltipBorder + title.height(); height += 4.0 * tooltipBorder + title.height();
ChartRectItem::resize(QSizeF(width, height)); QPixmap pixmap(lrint(width), lrint(height));
painter->setFont(font); pixmap.fill(Qt::transparent);
painter->setPen(QPen(tooltipFontColor)); // QPainter uses QPen to set text color! 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 x = 4.0 * tooltipBorder + tissues.width();
double y = 2.0 * tooltipBorder; double y = 2.0 * tooltipBorder;
double titleOffset = (width - title.width()) / 2.0; 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); 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); y += round(fontHeight);
for (auto &[s,w]: strings) { for (auto &[s,w]: strings) {
QRectF rect(x, y, w, fontHeight); QRectF rect(x, y, w, fontHeight);
painter->drawText(rect, s); painter.drawText(rect, s);
y += fontHeight; y += fontHeight;
} }
for (size_t i = 0; i < events.size(); ++i) { for (size_t i = 0; i < events.size(); ++i) {
QSizeF size = event_sizes[i]; QSizeF size = event_sizes[i];
auto &[s, pixmap] = events[i]; auto &[s, pixmap] = events[i];
painter->drawPixmap(lrint(x), lrint(y + (size.height() - pixmap.height())/2.0), pixmap, painter.drawPixmap(lrint(x), lrint(y + (size.height() - pixmap.height())/2.0), pixmap,
0, 0, pixmap.width(), pixmap.height()); 0, 0, pixmap.width(), pixmap.height());
QRectF rect(x + pixmap.width(), round(y + (size.height() - fontHeight) / 2.0), size.width(), fontHeight); 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(); y += size.height();
} }
setTextureDirty();
AnimatedChartRectItem::setPixmap(pixmap, animSpeed);
} }
void ToolTipItem::stopDrag(QPointF pos) void ToolTipItem::stopDrag(QPointF pos)

View file

@ -10,11 +10,11 @@
struct dive; struct dive;
struct plot_info; struct plot_info;
class ToolTipItem : public ChartRectItem { class ToolTipItem : public AnimatedChartRectItem {
public: public:
ToolTipItem(ChartView &view, double dpr); ToolTipItem(ChartView &view, double dpr);
void update(const dive *d, double dpr, int time, const plot_info &pInfo, void update(const dive *d, double dpr, int time, const plot_info &pInfo,
const std::vector<std::pair<QString, QPixmap>> &events, bool inPlanner); const std::vector<std::pair<QString, QPixmap>> &events, bool inPlanner, int animSpeed);
private: private:
QFont font; QFont font;
QFontMetrics fm; QFontMetrics fm;

View file

@ -150,6 +150,37 @@ void ChartRectItem::resize(QSizeF size)
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize); 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<QString> &text, bool center) : ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center) :
ChartPixmapItem(v, z), f(f), center(center) ChartPixmapItem(v, z), f(f), center(center)
{ {

View file

@ -98,6 +98,20 @@ private:
double radius; 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()! // Attention: text is only drawn after calling setColor()!
class ChartTextItem : public ChartPixmapItem { class ChartTextItem : public ChartPixmapItem {
public: public: