From ea0085fef6fb96146b59bce02225366e02f7b0f4 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Fri, 12 Jan 2024 22:26:32 +0100 Subject: [PATCH] profile: convert the "ruler item" to qt-quick Code is mostly based on the "tooltip item". The dragging code was slightly reworked to be more logical. A "disk item" was added for the handles. Signed-off-by: Berthold Stoeger --- Subsurface-mobile.pro | 7 +- profile-widget/CMakeLists.txt | 4 +- profile-widget/profilescene.cpp | 21 ++- profile-widget/profilescene.h | 9 +- profile-widget/profileview.cpp | 30 +++- profile-widget/profileview.h | 6 + profile-widget/ruleritem.cpp | 308 ++++++++++++++++---------------- profile-widget/ruleritem.h | 68 +++---- profile-widget/tooltipitem.cpp | 15 +- profile-widget/tooltipitem.h | 1 + profile-widget/zvalues.h | 1 + qt-quick/chartitem.cpp | 51 +++++- qt-quick/chartitem.h | 18 +- qt-quick/chartview.cpp | 2 +- 14 files changed, 313 insertions(+), 228 deletions(-) diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index d99198963..04fadc80d 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -182,7 +182,8 @@ SOURCES += subsurface-mobile-main.cpp \ profile-widget/tooltipitem.cpp \ profile-widget/divelineitem.cpp \ profile-widget/divetextitem.cpp \ - profile-widget/profileview.cpp + profile-widget/profileview.cpp \ + profile-widget/ruleritem.cpp HEADERS += \ commands/command_base.h \ @@ -345,7 +346,9 @@ HEADERS += \ profile-widget/divelineitem.h \ profile-widget/divepixmapcache.h \ profile-widget/divetextitem.h \ - profile-widget/profileview.h + profile-widget/profileview.h \ + profile-widget/ruleritem.h \ + profile-widget/profiletranslations.h RESOURCES += mobile-widgets/qml/mobile-resources.qrc \ mobile-widgets/3rdparty/icons.qrc \ diff --git a/profile-widget/CMakeLists.txt b/profile-widget/CMakeLists.txt index bc4dcaa92..c8f5846eb 100644 --- a/profile-widget/CMakeLists.txt +++ b/profile-widget/CMakeLists.txt @@ -23,6 +23,8 @@ set(SUBSURFACE_PROFILE_LIB_SRCS profiletranslations.h profileview.cpp profileview.h + ruleritem.cpp + ruleritem.h tankitem.cpp tankitem.h tooltipitem.h @@ -37,8 +39,6 @@ set(SUBSURFACE_PROFILE_LIB_SRCS ${SUBSURFACE_PROFILE_LIB_SRCS} divehandler.cpp divehandler.h - ruleritem.cpp - ruleritem.h ) endif () source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS}) diff --git a/profile-widget/profilescene.cpp b/profile-widget/profilescene.cpp index 9cddc28e3..d9014c5bd 100644 --- a/profile-widget/profilescene.cpp +++ b/profile-widget/profilescene.cpp @@ -612,22 +612,37 @@ int ProfileScene::timeAt(QPointF pos) const return lrint(timeAxis->valueAt(pos)); } -std::pair ProfileScene::minMaxTime() +std::pair ProfileScene::minMaxTime() const { return { timeAxis->minimum(), timeAxis->maximum() }; } -double ProfileScene::yToScreen(double y) +std::pair ProfileScene::minMaxX() const +{ + return timeAxis->screenMinMax(); +} + +std::pair ProfileScene::minMaxY() const +{ + return profileYAxis->screenMinMax(); +} + +double ProfileScene::yToScreen(double y) const { auto [min, max] = profileYAxis->screenMinMax(); return min + (max - min) * y; } -double ProfileScene::posAtTime(double time) +double ProfileScene::posAtTime(double time) const { return timeAxis->posAtValue(time); } +double ProfileScene::posAtDepth(double depth) const +{ + return profileYAxis->posAtValue(depth); +} + std::vector> ProfileScene::eventsAt(QPointF pos) const { std::vector> res; diff --git a/profile-widget/profilescene.h b/profile-widget/profilescene.h index c71dbc151..8e05b6597 100644 --- a/profile-widget/profilescene.h +++ b/profile-widget/profilescene.h @@ -51,9 +51,12 @@ public: double calcZoomPosition(double zoom, double originalPos, double delta); const plot_info &getPlotInfo() const; int timeAt(QPointF pos) const; // time in seconds - std::pair minMaxTime(); // time in seconds - double posAtTime(double time); // time in seconds - double yToScreen(double y); // For pictures: depth given in fration of displayed range. + std::pair minMaxTime() const; // time in seconds + std::pair minMaxX() const; // minimum and maximum x positions of canvas + std::pair minMaxY() const; // minimum and maximum y positions of canvas + double posAtTime(double time) const; // time in seconds + double posAtDepth(double depth) const; + double yToScreen(double y) const; // For pictures: depth given in fration of displayed range. std::vector> eventsAt(QPointF pos) const; const struct dive *d; diff --git a/profile-widget/profileview.cpp b/profile-widget/profileview.cpp index 37cab097d..58220a6b3 100644 --- a/profile-widget/profileview.cpp +++ b/profile-widget/profileview.cpp @@ -2,6 +2,7 @@ #include "profileview.h" #include "pictureitem.h" #include "profilescene.h" +#include "ruleritem.h" #include "tooltipitem.h" #include "zvalues.h" #include "core/dive.h" @@ -142,6 +143,7 @@ void ProfileView::resetPointers() { profileItem.reset(); tooltip.reset(); + ruler.reset(); pictures.clear(); highlightedPicture = nullptr; } @@ -170,6 +172,8 @@ void ProfileView::clear() //gases.clear(); if (tooltip) tooltip->setVisible(false); + if (ruler) + ruler->setVisible(false); empty = true; d = nullptr; dc = 0; @@ -217,9 +221,6 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) background = inPlanner ? QColor("#D7E3EF") : getColor(::BACKGROUND, false); profileItem->draw(size(), background, *profileScene); - //rulerItem->setVisible(prefs.rulergraph && currentState != PLAN && currentState != EDIT); - //rulerItem->setPlotInfo(d, profileScene->plotInfo); - //if ((currentState == EDIT || currentState == PLAN) && plannerModel) { //repositionDiveHandlers(); //plannerModel->deleteTemporaryPlan(); @@ -257,6 +258,15 @@ void ProfileView::plotDive(const struct dive *dIn, int dcIn, int flags) tooltip->setVisible(false); } + if (!ruler) + ruler = std::make_unique(*this, dpr); + if (prefs.rulergraph && !(flags & RenderFlags::PlanMode) && !(flags & RenderFlags::EditMode)) { + ruler->setVisible(true); + updateRuler(animSpeed); + } else { + ruler->setVisible(false); + } + // Reset animation. animation = make_anim([this](double progress) { anim(progress); }, animSpeed); } @@ -549,6 +559,20 @@ void ProfileView::updateTooltip(QPointF pos, bool plannerMode, int animSpeed) { if (tooltip) tooltip->anim(progress); update(); }, animSpeed); } +void ProfileView::rulerDragged() +{ + updateRuler(qPrefDisplay::animation_speed()); +} + +void ProfileView::updateRuler(int animSpeed) +{ + ruler->update(d, dpr, *profileScene, profileScene->getPlotInfo(), animSpeed); + + // Reset animation. + ruler_animation = make_anim([this](double progress) + { if (ruler) ruler->anim(progress); update(); }, animSpeed); +} + // Create a PictureEntry object and add its thumbnail to the scene if profile pictures are shown. ProfileView::PictureEntry::PictureEntry(offset_t offset, const QString &filename, ChartItemPtr thumbnail, double dpr, bool synchronous) : offset(offset), filename(filename), diff --git a/profile-widget/profileview.h b/profile-widget/profileview.h index de8aec20f..4f76be878 100644 --- a/profile-widget/profileview.h +++ b/profile-widget/profileview.h @@ -13,6 +13,7 @@ class ProfileAnimation; class ProfileScene; class ToolTipItem; struct picture; +class RulerItem; class ProfileView : public ChartView { Q_OBJECT @@ -40,6 +41,7 @@ public: void clear(); void resetZoom(); void anim(double fraction); + void rulerDragged(); // Called by the RulterItem when a handle was dragged. // For mobile Q_INVOKABLE void pinchStart(); @@ -84,6 +86,10 @@ private: void updateTooltip(QPointF pos, bool plannerMode, int animSpeed); std::unique_ptr tooltip_animation; + std::unique_ptr ruler; + void updateRuler(int animSpeed); + std::unique_ptr ruler_animation; + QPointF previousHoverMovePosition; // The list of pictures in this plot. The pictures are sorted by offset in seconds. diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp index 97b98267d..c4757c638 100644 --- a/profile-widget/ruleritem.cpp +++ b/profile-widget/ruleritem.cpp @@ -1,182 +1,184 @@ // SPDX-License-Identifier: GPL-2.0 -#include "profile-widget/ruleritem.h" +#include "ruleritem.h" +#include "profilescene.h" +#include "profileview.h" +#include "zvalues.h" #include "core/settings/qPrefTechnicalDetails.h" #include "core/profile.h" -#include -#include -#include -#include -#include +#include -RulerNodeItem2::RulerNodeItem2() : - pInfo(NULL), - idx(-1), - ruler(NULL), - timeAxis(NULL), - depthAxis(NULL) +static QColor handleBorderColor(Qt::red); +static QColor handleColor(0xff, 0, 0, 0x80); +static constexpr double handleRadius = 8.0; + +static QColor lineColor(Qt::black); +static constexpr double lineWidth = 1.0; + +static constexpr int tooltipBorder = 1; +static constexpr double tooltipBorderRadius = 2.0; // Radius of rounded corners +static QColor tooltipBorderColor(Qt::black); +static QColor tooltipColor(0xff, 0xff, 0xff, 190); +static QColor tooltipFontColor(Qt::black); + +class RulerItemHandle : public ChartDiskItem { - setRect(-8, -8, 16, 16); - setBrush(QColor(0xff, 0, 0, 127)); - setPen(QColor(Qt::red)); - setFlag(ItemIsMovable); - setFlag(ItemSendsGeometryChanges); - setFlag(ItemIgnoresTransformations); +public: + ProfileView &profileView; + double xpos; + // The first argument is passed twice, to avoid an downcast. Yes, that's silly. + RulerItemHandle(ChartView &view, ProfileView &profileView, double dpr) : + ChartDiskItem(view, ProfileZValue::RulerItem, + QPen(handleBorderColor, dpr), + QBrush(handleColor), + true), + profileView(profileView), + xpos(0.0) + { + } + // The call chain here is weird: this calls into the ProfileScene, which then calls + // back into the RulerItem. The reason is that the ProfileScene knows the current + // dive etc. This seems more robust than storing the current dive in the subobject. + void drag(QPointF pos) override + { + xpos = pos.x(); + profileView.rulerDragged(); + } +}; + +// duplicate code in tooltipitem.cpp +static QFont makeFont(double dpr) +{ + QFont font(qApp->font()); + if (dpr != 1.0) { + int pixelSize = font.pixelSize(); + if (pixelSize > 0) { + pixelSize = lrint(static_cast(pixelSize) * dpr); + font.setPixelSize(pixelSize); + } else { + font.setPointSizeF(font.pointSizeF() * dpr); + } + } + return font; } -void RulerNodeItem2::setPlotInfo(const plot_info &info) +static ChartItemPtr makeHandle(ProfileView &view, double dpr) { - pInfo = &info; - idx = 0; + auto res = view.createChartItem(view, dpr); + res->resize(handleRadius * dpr); + return res; } -void RulerNodeItem2::setRuler(RulerItem2 *r) +RulerItem::RulerItem(ProfileView &view, double dpr) : + line(view.createChartItem(ProfileZValue::RulerItem, + lineColor, lineWidth * dpr)), + handle1(makeHandle(view, dpr)), + handle2(makeHandle(view, dpr)), + tooltip(view.createChartItem(ProfileZValue::RulerItem, + QPen(tooltipBorderColor, lrint(tooltipBorder * dpr)), + QBrush(tooltipColor), tooltipBorderRadius * dpr, + false)), + font(makeFont(dpr)), + fm(font), + fontHeight(fm.height()) { - ruler = r; } -void RulerNodeItem2::recalculate() +void RulerItem::setVisible(bool visible) { - if (!pInfo || pInfo->nr <= 0) + line->setVisible(visible); + handle1->setVisible(visible); + handle2->setVisible(visible); + tooltip->setVisible(visible); +} + +// Binary search to find index at time stamp +static int get_idx_at_time(const plot_info &info, int time) +{ + auto entry = std::lower_bound(info.entry.begin(), info.entry.end(), time, + [](const plot_data &d, int time) + { return d.sec < time; }); + return entry != info.entry.end() ? entry - info.entry.begin() + : info.entry.size() - 1; +} + +void RulerItem::update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed) +{ + if (info.nr == 0) + return; // Nothing to display + + auto [minX, maxX] = scene.minMaxX(); + auto [minY, maxY] = scene.minMaxY(); + double x1 = std::clamp(handle1->xpos, minX, maxX); + double x2 = std::clamp(handle2->xpos, minX, maxX); + + int time1 = lrint(scene.timeAt(QPointF(x1, 0.0))); + int time2 = lrint(scene.timeAt(QPointF(x2, 0.0))); + + int idx1 = get_idx_at_time(info, time1); + int idx2 = get_idx_at_time(info, time2); + + double y1 = scene.posAtDepth(info.entry[idx1].depth); + double y2 = scene.posAtDepth(info.entry[idx2].depth); + + QPointF pos1(x1, y1); + QPointF pos2(x2, y2); + line->setLine(pos1, pos2); + handle1->setPos(pos1); + handle2->setPos(pos2); + + if (idx1 == idx2) { + tooltip->setVisible(false); return; - - const struct plot_data &last = pInfo->entry[pInfo->nr - 1]; - if (x() < 0) { - setPos(0, y()); - } else if (x() > timeAxis->posAtValue(last.sec)) { - setPos(timeAxis->posAtValue(last.sec), depthAxis->posAtValue(last.depth)); - } else { - idx = 0; - while (idx < pInfo->nr && timeAxis->posAtValue(pInfo->entry[idx].sec) < x()) - ++idx; - const struct plot_data &data = pInfo->entry[idx]; - setPos(timeAxis->posAtValue(data.sec), depthAxis->posAtValue(data.depth)); } -} + tooltip->setVisible(true); -void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event) -{ - qreal x = event->scenePos().x(); - if (x < 0.0) - x = 0.0; - setPos(x, event->scenePos().y()); - recalculate(); - ruler->recalculate(); -} + auto lines = compare_samples(d, info, idx1, idx2, 1); -RulerItem2::RulerItem2() : pInfo(NULL), - source(new RulerNodeItem2()), - dest(new RulerNodeItem2()), - timeAxis(NULL), - depthAxis(NULL), - textItemBack(new QGraphicsRectItem(this)), - textItem(new QGraphicsSimpleTextItem(this)) -{ - source->setRuler(this); - dest->setRuler(this); - textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations); - textItemBack->setBrush(QColor(0xff, 0xff, 0xff, 190)); - textItemBack->setPen(QColor(Qt::white)); - textItemBack->setFlag(QGraphicsItem::ItemIgnoresTransformations); - setPen(QPen(QColor(Qt::black), 0.0)); - connect(qPrefTechnicalDetails::instance(), &qPrefTechnicalDetails::rulergraphChanged, this, &RulerItem2::settingsChanged); -} - -void RulerItem2::settingsChanged(bool value) -{ - //ProfileWidget2 *profWidget = NULL; - //if (scene() && scene()->views().count()) - //profWidget = qobject_cast(scene()->views().first()); - - //setVisible( (profWidget && profWidget->currentState == ProfileWidget2::PROFILE) ? value : false); -} - -void RulerItem2::recalculate() -{ - QPointF tmp; - QFont font; - QFontMetrics fm(font); - - if (timeAxis == NULL || depthAxis == NULL || !pInfo || pInfo->nr == 0) - return; - - prepareGeometryChange(); - startPoint = mapFromItem(source, 0, 0); - endPoint = mapFromItem(dest, 0, 0); - - if (startPoint.x() > endPoint.x()) { - tmp = endPoint; - endPoint = startPoint; - startPoint = tmp; + double width = 0; + // Turn strings into QString/width pairs and increase width if needed + std::vector> strings; + strings.reserve(lines.size()); + for (auto &s_std: lines) { + auto s = QString::fromStdString(s_std); + int w = fm.size(Qt::TextSingleLine, s).width(); + width = std::max(width, static_cast(w)); + strings.push_back(std::make_pair(s, w)); } - QLineF line(startPoint, endPoint); - setLine(line); + width += 6.0 * tooltipBorder * dpr; - QString text; - for (const std::string &s: compare_samples(dive, *pInfo, source->idx, dest->idx, 1)) { - if (!text.isEmpty()) - text += '\n'; - text += QString::fromStdString(s); + double height = static_cast(strings.size()) * fontHeight + + 4.0 * tooltipBorder * dpr; + + 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 * dpr; + double y = 2.0 * tooltipBorder * dpr; + for (auto &[s,w]: strings) { + QRectF rect(x, y, w, fontHeight); + painter.drawText(rect, s); + y += fontHeight; } - // draw text - QGraphicsView *view = scene()->views().first(); - QPoint begin = view->mapFromScene(mapToScene(startPoint)); - textItem->setText(text); - qreal tgtX = startPoint.x(); - const qreal diff = begin.x() + textItem->boundingRect().width(); - // clamp so that the text doesn't go out of the screen to the right - if (diff > view->width()) { - begin.setX(lrint(begin.x() - (diff - view->width()))); - tgtX = mapFromScene(view->mapToScene(begin)).x(); - } - // always show the text bellow the lowest of the start and end points - qreal tgtY = (startPoint.y() >= endPoint.y()) ? startPoint.y() : endPoint.y(); - // this isn't exactly optimal, since we want to scale the 1.0, 4.0 distances as well - textItem->setPos(tgtX - 1.0, tgtY + 4.0); + tooltip->setPixmap(pixmap, animspeed); - // setup the text background - textItemBack->setVisible(startPoint.x() != endPoint.x()); - textItemBack->setPos(textItem->x(), textItem->y()); - textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height()); + if (pos2.x() < pos1.x()) + std::swap(pos1, pos2); + double xpos = width < maxX - minX ? + std::clamp(pos1.x() + handleRadius * dpr, 0.0, maxX - width) : 0.0; + + double ypos = height < maxY - minY ? + std::clamp(pos1.y() + handleRadius * dpr, 0.0, maxY - height) : 0.0; + + tooltip->setPos(QPointF(xpos, ypos)); } -RulerNodeItem2 *RulerItem2::sourceNode() const +void RulerItem::anim(double progress) { - return source; -} - -RulerNodeItem2 *RulerItem2::destNode() const -{ - return dest; -} - -void RulerItem2::setPlotInfo(const struct dive *d, const plot_info &info) -{ - dive = d; - pInfo = &info; - dest->setPlotInfo(info); - source->setPlotInfo(info); - dest->recalculate(); - source->recalculate(); - recalculate(); -} - -void RulerItem2::setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth) -{ - timeAxis = time; - depthAxis = depth; - dest->depthAxis = depth; - dest->timeAxis = time; - source->depthAxis = depth; - source->timeAxis = time; - recalculate(); -} - -void RulerItem2::setVisible(bool visible) -{ - QGraphicsLineItem::setVisible(visible); - source->setVisible(visible); - dest->setVisible(visible); + tooltip->anim(progress); } diff --git a/profile-widget/ruleritem.h b/profile-widget/ruleritem.h index a4e5725f3..2b4e7b112 100644 --- a/profile-widget/ruleritem.h +++ b/profile-widget/ruleritem.h @@ -2,59 +2,31 @@ #ifndef RULERITEM_H #define RULERITEM_H -#include -#include -#include -#include "profile-widget/divecartesianaxis.h" +#include "qt-quick/chartitem.h" -struct plot_data; +#include +#include + +class ProfileView; +class ProfileScene; +class RulerItemHandle; struct plot_info; -class RulerItem2; +struct dive; -class RulerNodeItem2 : public QObject, public QGraphicsEllipseItem { - Q_OBJECT - friend class RulerItem2; +class RulerItem { + ChartItemPtr line; + ChartItemPtr handle1, handle2; + ChartItemPtr tooltip; + QFont font; + QFontMetrics fm; + double fontHeight; + QPixmap title; public: - explicit RulerNodeItem2(); - void setRuler(RulerItem2 *r); - void setPlotInfo(const struct plot_info &info); - void recalculate(); - -private: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; - const struct plot_info *pInfo; - int idx; - RulerItem2 *ruler; - DiveCartesianAxis *timeAxis; - DiveCartesianAxis *depthAxis; -}; - -class RulerItem2 : public QObject, public QGraphicsLineItem { - Q_OBJECT -public: - explicit RulerItem2(); - void recalculate(); - - void setPlotInfo(const struct dive *d, const struct plot_info &pInfo); - RulerNodeItem2 *sourceNode() const; - RulerNodeItem2 *destNode() const; - void setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth); + RulerItem(ProfileView &view, double dpr); void setVisible(bool visible); - -public -slots: - void settingsChanged(bool toggled); - -private: - const struct dive *dive; - const struct plot_info *pInfo; - QPointF startPoint, endPoint; - RulerNodeItem2 *source, *dest; - QString text; - DiveCartesianAxis *timeAxis; - DiveCartesianAxis *depthAxis; - QGraphicsRectItem *textItemBack; - QGraphicsSimpleTextItem *textItem; + void update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed); + void anim(double progress); }; + #endif diff --git a/profile-widget/tooltipitem.cpp b/profile-widget/tooltipitem.cpp index 44317ffaa..80418026f 100644 --- a/profile-widget/tooltipitem.cpp +++ b/profile-widget/tooltipitem.cpp @@ -11,7 +11,6 @@ #include #include -#include static const int tooltipBorder = 2; static const double tooltipBorderRadius = 4.0; // Radius of rounded corners @@ -37,8 +36,8 @@ static QFont makeFont(double dpr) ToolTipItem::ToolTipItem(ChartView &view, double dpr) : AnimatedChartRectItem(view, ProfileZValue::ToolTipItem, - QPen(tooltipBorderColor, tooltipBorder), - QBrush(tooltipColor), tooltipBorderRadius, + QPen(tooltipBorderColor, lrint(tooltipBorder * dpr)), + QBrush(tooltipColor), tooltipBorderRadius * dpr, true), font(makeFont(dpr)), fm(font), @@ -131,9 +130,9 @@ void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &p } width += tissues.width(); - width += 6.0 * tooltipBorder; + width += 6.0 * tooltipBorder * dpr; height = std::max(height, static_cast(tissues.height())); - height += 4.0 * tooltipBorder + title.height(); + height += 4.0 * tooltipBorder * dpr + title.height(); QPixmap pixmap(lrint(width), lrint(height)); pixmap.fill(Qt::transparent); @@ -141,12 +140,12 @@ void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &p 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 x = 4.0 * tooltipBorder * dpr + tissues.width(); + double y = 2.0 * tooltipBorder * dpr; double titleOffset = (width - title.width()) / 2.0; 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 * dpr), lrint(y), tissues, 0, 0, tissues.width(), tissues.height()); y += round(fontHeight); for (auto &[s,w]: strings) { QRectF rect(x, y, w, fontHeight); diff --git a/profile-widget/tooltipitem.h b/profile-widget/tooltipitem.h index 2eac448f4..7ab975399 100644 --- a/profile-widget/tooltipitem.h +++ b/profile-widget/tooltipitem.h @@ -5,6 +5,7 @@ #include "qt-quick/chartitem.h" #include +#include #include struct dive; diff --git a/profile-widget/zvalues.h b/profile-widget/zvalues.h index 6791483a1..e499aa0d3 100644 --- a/profile-widget/zvalues.h +++ b/profile-widget/zvalues.h @@ -13,6 +13,7 @@ struct ProfileZValue { enum ZValues { Profile = 0, Pictures, + RulerItem, ToolTipItem, Count }; diff --git a/qt-quick/chartitem.cpp b/qt-quick/chartitem.cpp index 8cd3f47a9..d90dcec77 100644 --- a/qt-quick/chartitem.cpp +++ b/qt-quick/chartitem.cpp @@ -37,7 +37,7 @@ QRectF ChartItem::getRect() const return rect; } -void ChartItem::setPos(QPointF) +void ChartItem::drag(QPointF) { } @@ -119,6 +119,11 @@ void ChartPixmapItem::resize(QSizeF size) setTextureDirty(); } +void ChartPixmapItem::drag(QPointF pos) +{ + setPos(pos); +} + void ChartPixmapItem::setPos(QPointF pos) { rect.moveTopLeft(pos); @@ -158,6 +163,8 @@ ChartRectItem::~ChartRectItem() void ChartRectItem::resize(QSizeF size) { ChartPixmapItem::resize(size); + if (!painter) + return; img->fill(Qt::transparent); painter->setPen(pen); painter->setBrush(brush); @@ -175,7 +182,8 @@ 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()); + if (painter) + painter->drawPixmap(0, 0, p, 0, 0, p.width(), p.height()); setTextureDirty(); return; } @@ -194,10 +202,47 @@ 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()); + if (painter) + painter->drawPixmap(0, 0, pixmap, 0, 0, s.width(), s.height()); setTextureDirty(); } +ChartDiskItem::ChartDiskItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, bool dragable) : + ChartPixmapItem(v, z, dragable), + pen(pen), brush(brush) +{ +} + +ChartDiskItem::~ChartDiskItem() +{ +} + +void ChartDiskItem::resize(double radius) +{ + ChartPixmapItem::resize(QSizeF(2.0 * radius, 2.0 * radius)); + if (!painter) + return; + img->fill(Qt::transparent); + painter->setPen(pen); + painter->setBrush(brush); + QSize imgSize = img->size(); + int width = pen.width(); + QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width); + painter->drawEllipse(rect); +} + +// Moves the _center_ of the disk to given position. +void ChartDiskItem::setPos(QPointF pos) +{ + rect.moveCenter(pos); + setPositionDirty(); +} + +QPointF ChartDiskItem::getPos() const +{ + return rect.center(); +} + 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 47527ec05..890da9220 100644 --- a/qt-quick/chartitem.h +++ b/qt-quick/chartitem.h @@ -30,7 +30,7 @@ public: const bool dragable; // Item can be dragged with the mouse. Must be set in constructor. virtual ~ChartItem(); // Attention: must only be called by render thread. QRectF getRect() const; - virtual void setPos(QPointF pos); // Called when dragging the item + virtual void drag(QPointF pos); // Called when dragging the item virtual void stopDrag(QPointF pos); // Called when dragging the item finished protected: ChartItem(ChartView &v, size_t z, bool dragable = false); @@ -81,7 +81,8 @@ public: ChartPixmapItem(ChartView &v, size_t z, bool dragable = false); ~ChartPixmapItem(); - void setPos(QPointF pos) override; + virtual void setPos(QPointF pos); + void drag(QPointF pos) override; // calls setPos() by default void setScale(double scale); void render() override; protected: @@ -131,6 +132,19 @@ private: QSize originalSize; }; +// A solid disk, potentially with border. +class ChartDiskItem : public ChartPixmapItem { +public: + ChartDiskItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, bool dragable = false); + ~ChartDiskItem(); + void resize(double radius); + void setPos(QPointF pos) override; + QPointF getPos() const; +private: + QPen pen; + QBrush brush; +}; + // Attention: text is only drawn after calling setColor()! class ChartTextItem : public ChartPixmapItem { public: diff --git a/qt-quick/chartview.cpp b/qt-quick/chartview.cpp index 46ed04753..71db5d4f0 100644 --- a/qt-quick/chartview.cpp +++ b/qt-quick/chartview.cpp @@ -342,7 +342,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *event) QSizeF sceneSize = size(); if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0) return; - draggedItem->setPos(event->pos() - dragStartMouse + dragStartItem); + draggedItem->drag(event->pos() - dragStartMouse + dragStartItem); update(); } }