Skip to content

Commit

Permalink
profile: implement panning of profile
Browse files Browse the repository at this point in the history
When zoomed in, the profile position was moved by hovering with
the mouse. What a horrible user experience. This is especially
useless if we want to implement an interactive profile on mobile.

Instead, let the user start the panning with a mouse click. The
code is somewhat nasty, because the position is given as a
real in the [0,1] range, which represents all possible positions
from completely to the left to completely to the right.

This commit also removes the restriction that the planner handles
can only be moved when fully zoomed out. It is not completely
clear what the implications are. Let's see.

Signed-off-by: Berthold Stoeger <[email protected]>
  • Loading branch information
bstoeger authored and dirkhh committed Sep 3, 2022
1 parent 0d92ef2 commit edf2a2f
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import: allow import of divesites without UUID
profile: implement panning of the profile
planner: allow handle manipulation in zoomed in state
divelist: do not include planned versions of a dive if there is real data
desktop: fix key composition in tag widgets and dive site widget
desktop: use combobox for moving sensor between cylinders
Expand Down
10 changes: 10 additions & 0 deletions profile-widget/divecartesianaxis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,16 @@ double DiveCartesianAxis::valueAt(const QPointF &p) const
return fraction * (max - min) + min;
}

double DiveCartesianAxis::deltaToValue(double delta) const
{
QLineF m = line();
double screenSize = position == Position::Bottom ? m.x2() - m.x1()
: m.y2() - m.y1();
double axisSize = max - min;
double res = delta * axisSize / screenSize;
return ((position == Position::Bottom) == inverted) ? -res : res;
}

double DiveCartesianAxis::posAtValue(double value, double max, double min) const
{
QLineF m = line();
Expand Down
1 change: 1 addition & 0 deletions profile-widget/divecartesianaxis.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class DiveCartesianAxis : public QGraphicsLineItem {
std::pair<double, double> screenMinMax() const;
double valueAt(const QPointF &p) const;
double posAtValue(double value) const;
double deltaToValue(double delta) const; // For panning: turn a screen distance to delta-value
void setPosition(const QRectF &rect);
double screenPosition(double pos) const; // 0.0 = begin, 1.0 = end of axis, independent of represented values
double pointInRange(double pos) const; // Point on screen is in range of axis
Expand Down
25 changes: 20 additions & 5 deletions profile-widget/profilescene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "core/pref.h"
#include "core/profile.h"
#include "core/qthelper.h" // for decoMode()
#include "core/subsurface-float.h"
#include "core/subsurface-string.h"
#include "core/settings/qPrefDisplay.h"
#include "qt-models/diveplannermodel.h"
Expand Down Expand Up @@ -491,11 +492,9 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
percentageAxis->updateTicks(animSpeed);
animatedAxes.push_back(percentageAxis);

if (calcMax) {
double relStart = (1.0 - 1.0/zoom) * zoomedPosition;
double relEnd = relStart + 1.0/zoom;
timeAxis->setBounds(round(relStart * maxtime), round(relEnd * maxtime));
}
double relStart = (1.0 - 1.0/zoom) * zoomedPosition;
double relEnd = relStart + 1.0/zoom;
timeAxis->setBounds(round(relStart * maxtime), round(relEnd * maxtime));

// Find first and last plotInfo entry
int firstSecond = lrint(timeAxis->minimum());
Expand Down Expand Up @@ -627,3 +626,19 @@ void ProfileScene::draw(QPainter *painter, const QRect &pos,
}
painter->drawImage(pos, image);
}

// Calculate the new zoom position when the mouse is dragged by delta.
// This is annoyingly complex, because the zoom position is given as
// a real between 0 and 1.
double ProfileScene::calcZoomPosition(double zoom, double originalPos, double delta)
{
double factor = 1.0 - 1.0/zoom;
if (nearly_0(factor))
return 0.0;
double relStart = factor * originalPos;
double start = relStart * maxtime;
double newStart = start + timeAxis->deltaToValue(delta);
double newRelStart = newStart / maxtime;
double newPos = newRelStart / factor;
return std::clamp(newPos, 0.0, 1.0);
}
1 change: 1 addition & 0 deletions profile-widget/profilescene.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ProfileScene : public QGraphicsScene {
void draw(QPainter *painter, const QRect &pos,
const struct dive *d, int dc,
DivePlannerPointsModel *plannerModel = nullptr, bool inPlanner = false);
double calcZoomPosition(double zoom, double originalPos, double delta);

const struct dive *d;
int dc;
Expand Down
50 changes: 26 additions & 24 deletions profile-widget/profilewidget2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@
// We might add more constants here for easier customability.
static const double thumbnailBaseZValue = 100.0;

// Base of exponential zoom function: one wheel-click will increase the zoom by 15%.
static const double zoomFactor = 1.15;
static double calcZoom(int zoomLevel)
{
// Base of exponential zoom function: one wheel-click will increase the zoom by 15%.
constexpr double zoomFactor = 1.15;
return zoomLevel == 0 ? 1.0 : pow(zoomFactor, zoomLevel);
}

ProfileWidget2::ProfileWidget2(DivePlannerPointsModel *plannerModelIn, double dpr, QWidget *parent) : QGraphicsView(parent),
profileScene(new ProfileScene(dpr, false, false)),
Expand All @@ -55,6 +59,7 @@ ProfileWidget2::ProfileWidget2(DivePlannerPointsModel *plannerModelIn, double dp
d(nullptr),
dc(0),
empty(true),
panning(false),
#ifndef SUBSURFACE_MOBILE
mouseFollowerVertical(new DiveLineItem()),
mouseFollowerHorizontal(new DiveLineItem()),
Expand Down Expand Up @@ -202,7 +207,7 @@ void ProfileWidget2::plotDive(const struct dive *dIn, int dcIn, int flags)
DivePlannerPointsModel *model = currentState == EDIT || currentState == PLAN ? plannerModel : nullptr;
bool inPlanner = currentState == PLAN;

double zoom = zoomLevel == 0 ? 1.0 : pow(zoomFactor, zoomLevel);
double zoom = calcZoom(zoomLevel);
profileScene->plotDive(d, dc, model, inPlanner, flags & RenderFlags::Instant,
flags & RenderFlags::DontRecalculatePlotInfo,
shouldCalculateMax, zoom, zoomedPosition);
Expand Down Expand Up @@ -267,24 +272,22 @@ void ProfileWidget2::resizeEvent(QResizeEvent *event)
#ifndef SUBSURFACE_MOBILE
void ProfileWidget2::mousePressEvent(QMouseEvent *event)
{
if (zoomLevel)
return;
QGraphicsView::mousePressEvent(event);
if (currentState == PLAN || currentState == EDIT)
shouldCalculateMax = false;

if (!event->isAccepted()) {
panning = true;
panningOriginalMousePosition = mapToScene(event->pos()).x();
panningOriginalProfilePosition = zoomedPosition;
}
}

void ProfileWidget2::divePlannerHandlerClicked()
{
if (zoomLevel)
return;
shouldCalculateMax = false;
}

void ProfileWidget2::divePlannerHandlerReleased()
{
if (zoomLevel)
return;
if (currentState == EDIT)
emit stopMoved(1);
shouldCalculateMax = true;
Expand All @@ -293,9 +296,8 @@ void ProfileWidget2::divePlannerHandlerReleased()

void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event)
{
if (zoomLevel)
return;
QGraphicsView::mouseReleaseEvent(event);
panning = false;
if (currentState == PLAN || currentState == EDIT) {
shouldCalculateMax = true;
replot();
Expand All @@ -306,12 +308,6 @@ void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event)
void ProfileWidget2::setZoom(int level)
{
zoomLevel = level;
if (zoomLevel == 0) {
zoomedPosition = 0.0;
} else {
double pos = mapToScene(mapFromGlobal(QCursor::pos())).x();
zoomedPosition = pos / profileScene->width();
}
plotDive(d, dc, RenderFlags::DontRecalculatePlotInfo);
}

Expand All @@ -320,6 +316,8 @@ void ProfileWidget2::wheelEvent(QWheelEvent *event)
{
if (!d)
return;
if (panning)
return; // No change in zoom level while panning.
if (event->buttons() == Qt::LeftButton)
return;
if (event->angleDelta().y() > 0 && zoomLevel < 20)
Expand Down Expand Up @@ -348,13 +346,17 @@ void ProfileWidget2::mouseMoveEvent(QMouseEvent *event)
QGraphicsView::mouseMoveEvent(event);

QPointF pos = mapToScene(event->pos());
toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN);

if (zoomLevel != 0) {
zoomedPosition = pos.x() / profileScene->width();
plotDive(d, dc, RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo); // TODO: animations don't work when scrolling
if (panning) {
double oldPos = zoomedPosition;
zoomedPosition = profileScene->calcZoomPosition(calcZoom(zoomLevel),
panningOriginalProfilePosition,
panningOriginalMousePosition - pos.x());
if (oldPos != zoomedPosition)
plotDive(d, dc, RenderFlags::Instant | RenderFlags::DontRecalculatePlotInfo); // TODO: animations don't work when scrolling
}

toolTipItem->refresh(d, mapToScene(mapFromGlobal(QCursor::pos())), currentState == PLAN);

if (currentState == PLAN || currentState == EDIT) {
QRectF rect = profileScene->profileRegion;
auto [miny, maxy] = profileScene->profileYAxis->screenMinMax();
Expand Down
3 changes: 3 additions & 0 deletions profile-widget/profilewidget2.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ public
const struct dive *d;
int dc;
bool empty; // No dive shown.
bool panning; // Currently panning.
double panningOriginalMousePosition;
double panningOriginalProfilePosition;
#ifndef SUBSURFACE_MOBILE
DiveLineItem *mouseFollowerVertical;
DiveLineItem *mouseFollowerHorizontal;
Expand Down

0 comments on commit edf2a2f

Please sign in to comment.