summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKnud Dollereder <knud.dollereder@qt.io>2019-03-27 15:13:11 +0100
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2019-03-28 08:53:24 +0000
commitdf6afb91041d5225448a13aaf3bbb6f11137b0c0 (patch)
tree3809a1686ce9c5eed9697a8d868a49225813c6e0
parentc9807788db6219287ab1bff001fde7592171d7a8 (diff)
Improve viewport navigation
Pan/Zoom with mouse an shortcuts Keep the playhead in valid position Scroll the GraphicsView by dragging the playhead out of bounds Add style options for playhead and shortcuts Minor cleanup Change-Id: I368f170974c4842c725032da9f8faf7653c31d1f Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
-rw-r--r--examples/curveeditorapp/examplecurvemodel.cpp3
-rw-r--r--src/curveeditor/animationcurve.cpp13
-rw-r--r--src/curveeditor/curveeditorstyle.h36
-rw-r--r--src/curveeditor/detail/curveeditorstyledialog.cpp30
-rw-r--r--src/curveeditor/detail/curveeditorstyledialog.h12
-rw-r--r--src/curveeditor/detail/graphicsscene.cpp34
-rw-r--r--src/curveeditor/detail/graphicsscene.h7
-rw-r--r--src/curveeditor/detail/graphicsview.cpp233
-rw-r--r--src/curveeditor/detail/graphicsview.h46
-rw-r--r--src/curveeditor/detail/playhead.cpp129
-rw-r--r--src/curveeditor/detail/playhead.h21
11 files changed, 438 insertions, 126 deletions
diff --git a/examples/curveeditorapp/examplecurvemodel.cpp b/examples/curveeditorapp/examplecurvemodel.cpp
index 414b0b5..2402e7b 100644
--- a/examples/curveeditorapp/examplecurvemodel.cpp
+++ b/examples/curveeditorapp/examplecurvemodel.cpp
@@ -56,7 +56,7 @@ CurveEditorStyle ExampleCurveModel::style() const
out.backgroundAlternateBrush = QBrush(QColor(0, 0, 50));
out.fontColor = QColor(255, 255, 255);
out.gridColor = QColor(114, 116, 118);
- out.canvasMargin = 5;
+ out.canvasMargin = 15;
out.zoomInWidth = 99;
out.zoomInHeight = 99;
out.timeAxisHeight = 40;
@@ -67,7 +67,6 @@ CurveEditorStyle ExampleCurveModel::style() const
out.valueAxisWidth = 60;
out.valueOffsetTop = 10;
out.valueOffsetBottom = 10;
- out.playheadColor = QColor(255, 255, 0);
out.handleStyle.size = 12;
out.handleStyle.lineWidth = 1;
out.handleStyle.color = QColor(255, 255, 255);
diff --git a/src/curveeditor/animationcurve.cpp b/src/curveeditor/animationcurve.cpp
index acb64ce..8e2d522 100644
--- a/src/curveeditor/animationcurve.cpp
+++ b/src/curveeditor/animationcurve.cpp
@@ -39,7 +39,8 @@ AnimationCurve::AnimationCurve(const std::vector<Keyframe> &frames)
, m_minY(std::numeric_limits<double>::max())
, m_maxY(std::numeric_limits<double>::lowest())
{
- if (m_frames.size() >= 2) {
+ if (isValid()) {
+
for (auto e : extrema()) {
if (m_minY > e.y())
@@ -48,12 +49,20 @@ AnimationCurve::AnimationCurve(const std::vector<Keyframe> &frames)
if (m_maxY < e.y())
m_maxY = e.y();
}
+
+ for (auto &frame : qAsConst(m_frames)) {
+ if (frame.position().y() < m_minY)
+ m_minY = frame.position().y();
+
+ if (frame.position().y() > m_maxY)
+ m_maxY = frame.position().y();
+ }
}
}
bool AnimationCurve::isValid() const
{
- return !m_frames.empty();
+ return m_frames.size() >= 2;
}
double AnimationCurve::minimumTime() const
diff --git a/src/curveeditor/curveeditorstyle.h b/src/curveeditor/curveeditorstyle.h
index f60948a..ed82317 100644
--- a/src/curveeditor/curveeditorstyle.h
+++ b/src/curveeditor/curveeditorstyle.h
@@ -30,6 +30,7 @@
#include <QColor>
#include <QDialog>
#include <QIcon>
+#include <QKeySequence>
namespace DesignTools {
@@ -64,47 +65,44 @@ struct CurveItemStyleOption
QColor selectionColor = QColor(200, 200, 200);
};
+struct PlayheadStyleOption
+{
+ double width = 20.0;
+ double radius = 4.0;
+ QColor color = QColor(200, 200, 0);
+};
+
+struct Shortcuts
+{
+ QKeySequence zoom = QKeySequence(Qt::AltModifier, Qt::RightButton);
+ QKeySequence pan = QKeySequence(Qt::AltModifier, Qt::MiddleButton);
+};
+
struct CurveEditorStyle
{
- QBrush backgroundBrush = QBrush(QColor(5, 0, 100));
+ Shortcuts shortcuts;
+ QBrush backgroundBrush = QBrush(QColor(5, 0, 100));
QBrush backgroundAlternateBrush = QBrush(QColor(0, 0, 50));
-
QColor fontColor = QColor(200, 200, 200);
-
QColor gridColor = QColor(128, 128, 128);
-
double canvasMargin = 5.0;
-
int zoomInWidth = 100;
-
int zoomInHeight = 100;
-
double timeAxisHeight = 40.0;
-
double timeOffsetLeft = 10.0;
-
double timeOffsetRight = 10.0;
-
QColor rangeBarColor = QColor(128, 128, 128);
-
QColor rangeBarCapsColor = QColor(50, 50, 255);
-
double valueAxisWidth = 60.0;
-
double valueOffsetTop = 10.0;
-
double valueOffsetBottom = 10.0;
- QColor playheadColor = QColor(200, 200, 0);
-
HandleItemStyleOption handleStyle;
-
KeyframeItemStyleOption keyframeStyle;
-
CurveItemStyleOption curveStyle;
-
TreeItemStyleOption treeItemStyle;
+ PlayheadStyleOption playhead;
};
inline QPixmap pixmapFromIcon(const QIcon &icon, const QSize &size, const QColor &color)
diff --git a/src/curveeditor/detail/curveeditorstyledialog.cpp b/src/curveeditor/detail/curveeditorstyledialog.cpp
index fc34737..7f2e288 100644
--- a/src/curveeditor/detail/curveeditorstyledialog.cpp
+++ b/src/curveeditor/detail/curveeditorstyledialog.cpp
@@ -65,7 +65,6 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
, m_valueAxisWidth(new QDoubleSpinBox())
, m_valueOffsetTop(new QDoubleSpinBox())
, m_valueOffsetBottom(new QDoubleSpinBox())
- , m_playhead(new ColorControl(style.playheadColor))
, m_handleSize(new QDoubleSpinBox())
, m_handleLineWidth(new QDoubleSpinBox())
, m_handleColor(new ColorControl(style.handleStyle.color))
@@ -76,6 +75,11 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
, m_curveWidth(new QDoubleSpinBox())
, m_curveColor(new ColorControl(style.curveStyle.color))
, m_curveSelectionColor(new ColorControl(style.curveStyle.selectionColor))
+ , m_treeMargins(new QDoubleSpinBox())
+ , m_playheadWidth(new QDoubleSpinBox())
+ , m_playheadRadius(new QDoubleSpinBox())
+ , m_playheadColor(new ColorControl(style.playhead.color))
+
{
m_canvasMargin->setValue(style.canvasMargin);
m_zoomInWidth->setValue(style.zoomInWidth);
@@ -90,6 +94,9 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
m_handleLineWidth->setValue(style.handleStyle.lineWidth);
m_keyframeSize->setValue(style.keyframeStyle.size);
m_curveWidth->setValue(style.curveStyle.width);
+ m_treeMargins->setValue(style.treeItemStyle.margins);
+ m_playheadWidth->setValue(style.playhead.width);
+ m_playheadRadius->setValue(style.playhead.radius);
connect(m_printButton, &QPushButton::released, this, &CurveEditorStyleDialog::printStyle);
@@ -115,7 +122,6 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
connect(m_valueAxisWidth, doubleSignal, doubleChanged);
connect(m_valueOffsetTop, doubleSignal, doubleChanged);
connect(m_valueOffsetBottom, doubleSignal, doubleChanged);
- connect(m_playhead, &ColorControl::valueChanged, colorChanged);
connect(m_handleSize, doubleSignal, doubleChanged);
connect(m_handleLineWidth, doubleSignal, doubleChanged);
connect(m_handleColor, &ColorControl::valueChanged, colorChanged);
@@ -126,6 +132,10 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
connect(m_curveWidth, doubleSignal, doubleChanged);
connect(m_curveColor, &ColorControl::valueChanged, colorChanged);
connect(m_curveSelectionColor, &ColorControl::valueChanged, colorChanged);
+ connect(m_treeMargins, doubleSignal, doubleChanged);
+ connect(m_playheadWidth, doubleSignal, doubleChanged);
+ connect(m_playheadRadius, doubleSignal, doubleChanged);
+ connect(m_playheadColor, &ColorControl::valueChanged, colorChanged);
auto *box = new QVBoxLayout;
box->addLayout(createRow("Background Color", m_background));
@@ -143,7 +153,6 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
box->addLayout(createRow("Value Axis Width", m_valueAxisWidth));
box->addLayout(createRow("Value Axis Top Offset", m_valueOffsetTop));
box->addLayout(createRow("Value Axis Bottom Offset", m_valueOffsetBottom));
- box->addLayout(createRow("Playhead Color", m_playhead));
box->addLayout(createRow("Handle Size", m_handleSize));
box->addLayout(createRow("Handle Line Width", m_handleLineWidth));
box->addLayout(createRow("Handle Color", m_handleColor));
@@ -154,6 +163,10 @@ CurveEditorStyleDialog::CurveEditorStyleDialog(CurveEditorStyle &style, QWidget
box->addLayout(createRow("Curve Width", m_curveWidth));
box->addLayout(createRow("Curve Color", m_curveColor));
box->addLayout(createRow("Curve Selection Color", m_curveSelectionColor));
+ box->addLayout(createRow("Treeview margins", m_treeMargins));
+ box->addLayout(createRow("Playhead width", m_playheadWidth));
+ box->addLayout(createRow("Playhead radius", m_playheadRadius));
+ box->addLayout(createRow("Playhead color", m_playheadColor));
box->addWidget(m_printButton);
setLayout(box);
@@ -177,7 +190,6 @@ CurveEditorStyle CurveEditorStyleDialog::style() const
style.valueAxisWidth = m_valueAxisWidth->value();
style.valueOffsetTop = m_valueOffsetTop->value();
style.valueOffsetBottom = m_valueOffsetBottom->value();
- style.playheadColor = m_playhead->value();
style.handleStyle.size = m_handleSize->value();
style.handleStyle.lineWidth = m_handleLineWidth->value();
style.handleStyle.color = m_handleColor->value();
@@ -188,6 +200,11 @@ CurveEditorStyle CurveEditorStyleDialog::style() const
style.curveStyle.width = m_curveWidth->value();
style.curveStyle.color = m_curveColor->value();
style.curveStyle.selectionColor = m_curveSelectionColor->value();
+ style.treeItemStyle.margins = m_treeMargins->value();
+ style.playhead.width = m_playheadWidth->value();
+ style.playhead.radius = m_playheadRadius->value();
+ style.playhead.color = m_playheadColor->value();
+
return style;
}
@@ -224,7 +241,6 @@ void CurveEditorStyleDialog::printStyle()
qDebug().nospace() << "out.valueAxisWidth = " << s.valueAxisWidth << ";";
qDebug().nospace() << "out.valueOffsetTop = " << s.valueOffsetTop << ";";
qDebug().nospace() << "out.valueOffsetBottom = " << s.valueOffsetBottom << ";";
- qDebug().nospace() << "out.playheadColor = " << toString(s.playheadColor) << ";";
qDebug().nospace() << "out.handleStyle.size = " << s.handleStyle.size << ";";
qDebug().nospace() << "out.handleStyle.lineWidth = " << s.handleStyle.lineWidth << ";";
qDebug().nospace() << "out.handleStyle.color = " << toString(s.handleStyle.color) << ";";
@@ -238,6 +254,10 @@ void CurveEditorStyleDialog::printStyle()
qDebug().nospace() << "out.curveStyle.color = " << toString(s.curveStyle.color) << ";";
qDebug().nospace() << "out.curveStyle.selectionColor = "
<< toString(s.curveStyle.selectionColor) << ";";
+ qDebug().nospace() << "out.treeItemStyle.margins = " << s.treeItemStyle.margins << ";";
+ qDebug().nospace() << "out.playheadStyle.width = " << s.playhead.width << ";";
+ qDebug().nospace() << "out.playheadStyle.radius = " << s.playhead.radius << ";";
+ qDebug().nospace() << "out.playheadStyle.color = " << toString(s.playhead.color) << ";";
qDebug().nospace() << "return out;";
qDebug() << "";
}
diff --git a/src/curveeditor/detail/curveeditorstyledialog.h b/src/curveeditor/detail/curveeditorstyledialog.h
index 645f545..ad5a28e 100644
--- a/src/curveeditor/detail/curveeditorstyledialog.h
+++ b/src/curveeditor/detail/curveeditorstyledialog.h
@@ -88,8 +88,6 @@ private:
QDoubleSpinBox *m_valueOffsetBottom;
- ColorControl *m_playhead;
-
// HandleItem
QDoubleSpinBox *m_handleSize;
@@ -112,6 +110,16 @@ private:
ColorControl *m_curveColor;
ColorControl *m_curveSelectionColor;
+
+ // TreeItem
+ QDoubleSpinBox *m_treeMargins;
+
+ // Playhead
+ QDoubleSpinBox *m_playheadWidth;
+
+ QDoubleSpinBox *m_playheadRadius;
+
+ ColorControl *m_playheadColor;
};
} // End namespace DesignTools.
diff --git a/src/curveeditor/detail/graphicsscene.cpp b/src/curveeditor/detail/graphicsscene.cpp
index 45e02c8..3fd2b71 100644
--- a/src/curveeditor/detail/graphicsscene.cpp
+++ b/src/curveeditor/detail/graphicsscene.cpp
@@ -26,6 +26,7 @@
#include "graphicsscene.h"
#include "animationcurve.h"
#include "curveitem.h"
+#include "graphicsview.h"
#include <QGraphicsSceneMouseEvent>
@@ -37,6 +38,11 @@ GraphicsScene::GraphicsScene(QObject *parent)
, m_limits()
{}
+bool GraphicsScene::empty() const
+{
+ return items().empty();
+}
+
double GraphicsScene::minimumTime() const
{
return limits().left();
@@ -63,6 +69,24 @@ void GraphicsScene::addCurveItem(CurveItem *item)
addItem(item);
}
+void GraphicsScene::setComponentTransform(const QTransform &transform)
+{
+ QRectF bounds;
+ const auto itemList = items();
+ for (auto *item : itemList) {
+ if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item))
+ bounds = bounds.united(curveItem->setComponentTransform(transform));
+ }
+
+ if (bounds.isNull()) {
+ if (GraphicsView *gview = graphicsView())
+ bounds = gview->defaultRasterRect();
+ }
+
+ if (bounds.isValid())
+ setSceneRect(bounds);
+}
+
void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);
@@ -95,6 +119,16 @@ void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
}
}
+GraphicsView *GraphicsScene::graphicsView() const
+{
+ const QList<QGraphicsView *> viewList = views();
+ for (auto &&view : viewList) {
+ if (GraphicsView *gview = qobject_cast<GraphicsView *>(view))
+ return gview;
+ }
+ return nullptr;
+}
+
QRectF GraphicsScene::limits() const
{
if (m_dirty) {
diff --git a/src/curveeditor/detail/graphicsscene.h b/src/curveeditor/detail/graphicsscene.h
index 7f80436..4725d33 100644
--- a/src/curveeditor/detail/graphicsscene.h
+++ b/src/curveeditor/detail/graphicsscene.h
@@ -31,6 +31,7 @@ namespace DesignTools {
class AnimationCurve;
class CurveItem;
+class GraphicsView;
class GraphicsScene : public QGraphicsScene
{
@@ -42,6 +43,8 @@ signals:
public:
GraphicsScene(QObject *parent = nullptr);
+ bool empty() const;
+
double minimumTime() const;
double maximumTime() const;
@@ -52,6 +55,8 @@ public:
void addCurveItem(CurveItem *item);
+ void setComponentTransform(const QTransform& transform);
+
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
@@ -60,6 +65,8 @@ protected:
private:
using QGraphicsScene::addItem;
+ GraphicsView * graphicsView() const;
+
QRectF limits() const;
void clearSelection();
diff --git a/src/curveeditor/detail/graphicsview.cpp b/src/curveeditor/detail/graphicsview.cpp
index 29251aa..245194f 100644
--- a/src/curveeditor/detail/graphicsview.cpp
+++ b/src/curveeditor/detail/graphicsview.cpp
@@ -22,14 +22,18 @@
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
+
#include "graphicsview.h"
#include "curveeditormodel.h"
#include "curveitem.h"
#include "utils.h"
#include <QAction>
+#include <QApplication>
+#include <QKeySequence>
#include <QMenu>
#include <QResizeEvent>
+#include <QScrollBar>
#include <cmath>
@@ -40,7 +44,7 @@ GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent)
, m_scene()
, m_style(model->style())
, m_model(model)
- , m_playhead()
+ , m_playhead(this)
, m_dialog(m_style)
, m_zoomX(0.0)
, m_zoomY(0.0)
@@ -49,6 +53,8 @@ GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent)
setScene(&m_scene);
setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ setResizeAnchor(QGraphicsView::NoAnchor);
+ setTransformationAnchor(QGraphicsView::NoAnchor);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
@@ -76,6 +82,62 @@ CurveEditorStyle GraphicsView::editorStyle() const
return m_style;
}
+double GraphicsView::minimumTime() const
+{
+ bool check = m_model->minimumTime() < m_scene.minimumTime();
+ return check ? m_model->minimumTime() : m_scene.minimumTime();
+}
+
+double GraphicsView::maximumTime() const
+{
+ bool check = m_model->maximumTime() > m_scene.maximumTime();
+ return check ? m_model->maximumTime() : m_scene.maximumTime();
+}
+
+double GraphicsView::minimumValue() const
+{
+ return m_scene.empty() ? -1.0 : m_scene.minimumValue();
+}
+
+double GraphicsView::maximumValue() const
+{
+ return m_scene.empty() ? 1.0 : m_scene.maximumValue();
+}
+
+QRectF GraphicsView::canvasRect() const
+{
+ QRect r = viewport()->rect().adjusted(
+ m_style.valueAxisWidth + m_style.canvasMargin,
+ m_style.timeAxisHeight + m_style.canvasMargin,
+ -m_style.canvasMargin,
+ -m_style.canvasMargin);
+
+ return mapToScene(r).boundingRect();
+}
+
+QRectF GraphicsView::timeScaleRect() const
+{
+ QRect vp(viewport()->rect());
+ QPoint tl = vp.topLeft() + QPoint(m_style.valueAxisWidth, 0);
+ QPoint br = vp.topRight() + QPoint(0, m_style.timeAxisHeight);
+ return mapToScene(QRect(tl, br)).boundingRect();
+}
+
+QRectF GraphicsView::valueScaleRect() const
+{
+ QRect vp(viewport()->rect());
+ QPoint tl = vp.topLeft() + QPoint(0, m_style.timeAxisHeight);
+ QPoint br = vp.bottomLeft() + QPoint(m_style.valueAxisWidth, 0);
+ return mapToScene(QRect(tl, br)).boundingRect();
+}
+
+QRectF GraphicsView::defaultRasterRect() const
+{
+ QPointF topLeft(mapTimeToX(minimumTime()), mapValueToY(maximumValue()));
+ QPointF bottomRight(mapTimeToX(maximumTime()), mapValueToY(minimumValue()));
+ return QRectF(topLeft, bottomRight);
+}
+
void GraphicsView::setStyle(const CurveEditorStyle &style)
{
m_style = style;
@@ -104,7 +166,8 @@ void GraphicsView::zoomY(double zoom)
void GraphicsView::setCurrentFrame(int frame)
{
- m_playhead.moveToFrame(frame, this);
+ int clampedFrame = clamp(frame, m_model->minimumTime(), m_model->maximumTime());
+ m_playhead.moveToFrame(clampedFrame, this);
viewport()->update();
}
@@ -120,33 +183,81 @@ void GraphicsView::reset(const std::vector<CurveItem *> &items)
void GraphicsView::resizeEvent(QResizeEvent *event)
{
- applyZoom(m_zoomX, m_zoomY);
QGraphicsView::resizeEvent(event);
+ applyZoom(m_zoomX, m_zoomY);
}
void GraphicsView::mousePressEvent(QMouseEvent *event)
{
- QPoint viewPos = viewport()->mapFromGlobal(event->globalPos());
- if (!m_playhead.mousePress(mapToScene(viewPos)))
+ if (event->button() == Qt::LeftButton && event->modifiers().testFlag(Qt::NoModifier)) {
+
+ QPointF pos = mapToScene(event->pos());
+ if (timeScaleRect().contains(pos))
+ setCurrentFrame(std::round(mapXtoTime(pos.x())));
+ }
+
+ QKeySequence eventSequence(event->modifiers(), event->buttons());
+ if (eventSequence == m_style.shortcuts.zoom || eventSequence == m_style.shortcuts.pan) {
+
+ m_mouse = event->globalPos();
+ m_start = event->globalPos();
+ event->accept();
+ return;
+ }
+
+ if (!m_playhead.mousePress(globalToScene(event->globalPos())))
QGraphicsView::mousePressEvent(event);
}
void GraphicsView::mouseMoveEvent(QMouseEvent *event)
{
- QPoint viewPos = viewport()->mapFromGlobal(event->globalPos());
- if (!m_playhead.mouseMove(mapToScene(viewPos), this))
+ QPointF delta = event->globalPos() - m_mouse;
+ if (!m_mouse.isNull() && delta.manhattanLength() >= QApplication::startDragDistance()) {
+
+ QKeySequence eventSequence(event->modifiers(), event->buttons());
+
+ if (eventSequence == m_style.shortcuts.zoom) {
+
+ double bigger = std::abs(delta.x()) > std::abs(delta.y()) ? delta.x() : delta.y();
+ double factor = bigger / this->width();
+ double zoomX = m_zoomX + factor;
+ applyZoom(zoomX, m_zoomY, m_start);
+ m_mouse = event->globalPos();
+ return;
+
+ } else if (eventSequence == m_style.shortcuts.pan) {
+
+ scrollContent(-delta.x(), delta.y());
+ m_playhead.resize(this);
+ m_mouse = event->globalPos();
+ return;
+ }
+ }
+
+ if (!m_playhead.mouseMove(globalToScene(event->globalPos()), this))
QGraphicsView::mouseMoveEvent(event);
}
void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
+ m_mouse = QPoint();
+ m_start = QPoint();
QGraphicsView::mouseReleaseEvent(event);
m_playhead.mouseRelease(this);
}
+void GraphicsView::wheelEvent(QWheelEvent *event)
+{
+ if (event->modifiers().testFlag(Qt::AltModifier))
+ return;
+
+ QGraphicsView::wheelEvent(event);
+}
+
void GraphicsView::contextMenuEvent(QContextMenuEvent *event)
{
- Q_UNUSED(event);
+ if (event->modifiers() != Qt::NoModifier)
+ return;
auto openStyleEditor = [this]() { m_dialog.show(); };
@@ -159,19 +270,17 @@ void GraphicsView::contextMenuEvent(QContextMenuEvent *event)
void GraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
{
- auto gap = QRectF(rect.topLeft(), QSizeF(m_style.valueAxisWidth, m_style.timeAxisHeight));
-
- m_playhead.paint(painter);
-
- auto abscissa = QRectF(gap.topRight(), rect.topRight() + QPointF(0.0, gap.height()));
+ QRectF abscissa = timeScaleRect();
if (abscissa.isValid())
drawTimeScale(painter, abscissa);
- auto ordinate = QRectF(gap.bottomLeft(), rect.bottomLeft() + QPointF(gap.width(), 0.0));
+ auto ordinate = valueScaleRect();
if (ordinate.isValid())
drawValueScale(painter, ordinate);
- painter->fillRect(gap, m_style.backgroundAlternateBrush);
+ m_playhead.paint(painter, this);
+
+ painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()), m_style.backgroundAlternateBrush);
}
void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
@@ -184,67 +293,86 @@ void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
drawExtremaY(painter, rect);
}
-int GraphicsView::mapTimeToX(double time)
+int GraphicsView::mapTimeToX(double time) const
{
return std::round(time * scaleX(m_transform));
}
-int GraphicsView::mapValueToY(double y)
+int GraphicsView::mapValueToY(double y) const
{
return std::round(y * scaleY(m_transform));
}
-double GraphicsView::mapXtoTime(int x)
+double GraphicsView::mapXtoTime(int x) const
{
return static_cast<double>(x) / scaleX(m_transform);
}
-double GraphicsView::mapYtoValue(int y)
+double GraphicsView::mapYtoValue(int y) const
{
return static_cast<double>(y) / scaleY(m_transform);
}
-void GraphicsView::applyZoom(double x, double y)
+QPointF GraphicsView::globalToScene(const QPoint &point) const
+{
+ return mapToScene(viewport()->mapFromGlobal(point));
+}
+
+QPointF GraphicsView::globalToRaster(const QPoint &point) const
+{
+ QPointF scene = globalToScene(point);
+ return QPointF(mapXtoTime(scene.x()), mapYtoValue(scene.y()));
+}
+
+void GraphicsView::scrollContent(double x, double y)
{
- m_zoomX = x;
- m_zoomY = y;
+ auto *hs = horizontalScrollBar();
+ auto *vs = verticalScrollBar();
+ hs->setValue(hs->value() + x);
+ vs->setValue(vs->value() + y);
+}
+
+void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
+{
+ QPointF pivotRaster(globalToRaster(pivot));
+
+ m_zoomX = clamp(x, 0.0, 1.0);
+ m_zoomY = clamp(y, 0.0, 1.0);
+
+ double minTime = minimumTime();
+ double maxTime = maximumTime();
+
+ double minValue = minimumValue();
+ double maxValue = maximumValue();
- double canvasWidth = rect().width() - m_style.valueAxisWidth - 2 * m_style.canvasMargin - rect().width() / 20.0;
- double xZoomedOut = canvasWidth / (m_model->maximumTime() - m_model->minimumTime());
+ QRectF canvas = canvasRect();
+
+ double xZoomedOut = canvas.width() / (maxTime - minTime);
double xZoomedIn = m_style.zoomInWidth;
double scaleX = lerp(clamp(m_zoomX, 0.0, 1.0), xZoomedOut, xZoomedIn);
- double canvasHeight = rect().height() - m_style.timeAxisHeight - 2 * m_style.canvasMargin - rect().height() / 10;
- double yZoomedOut = canvasHeight / (m_scene.maximumValue() - m_scene.minimumValue());
+ double yZoomedOut = canvas.height() / (maxValue - minValue);
double yZoomedIn = m_style.zoomInHeight;
double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn);
m_transform = QTransform::fromScale(scaleX, scaleY);
- QRectF sceneBounds;
- const auto itemList = items();
- for (auto *item : itemList) {
- if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item))
- sceneBounds = sceneBounds.united(curveItem->setComponentTransform(m_transform));
- }
-
- updateSceneRect(sceneBounds);
-
- m_playhead.resize(this);
-}
-
-void GraphicsView::updateSceneRect(const QRectF &rect)
-{
- if (rect.isValid())
- scene()->setSceneRect(rect);
+ m_scene.setComponentTransform(m_transform);
- QRectF sr = scene()->sceneRect().adjusted(
+ QRectF sr = m_scene.sceneRect().adjusted(
-m_style.valueAxisWidth - m_style.canvasMargin,
-m_style.timeAxisHeight - m_style.canvasMargin,
m_style.canvasMargin,
m_style.canvasMargin);
setSceneRect(sr);
+
+ m_playhead.resize(this);
+
+ if (!pivot.isNull()) {
+ QPointF deltaTransformed = pivotRaster - globalToRaster(pivot);
+ scrollContent(mapTimeToX(deltaTransformed.x()), mapValueToY(deltaTransformed.y()));
+ }
}
void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect)
@@ -266,7 +394,7 @@ void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect)
painter->setPen(m_style.gridColor);
double timeIncrement = timeLabelInterval(painter, m_model->maximumTime());
- for (double i = m_model->minimumTime(); i <= m_model->maximumTime(); i += timeIncrement)
+ for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
drawVerticalLine(mapTimeToX(i));
painter->restore();
@@ -287,6 +415,9 @@ void GraphicsView::drawExtremaX(QPainter *painter, const QRectF &rect)
void GraphicsView::drawExtremaY(QPainter *painter, const QRectF &rect)
{
+ if (m_scene.empty())
+ return;
+
auto drawHorizontalLine = [rect, painter](double position) {
painter->drawLine(rect.left(), position, rect.right(), position);
};
@@ -316,12 +447,13 @@ void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect)
QRect textRect = fm.boundingRect(timeText);
textRect.moveCenter(QPoint(position, rect.center().y()));
+
painter->drawText(textRect, Qt::AlignCenter, timeText);
painter->drawLine(position, rect.bottom() - 2, position, textRect.bottom() + 2);
};
- double timeIncrement = timeLabelInterval(painter, m_model->maximumTime());
- for (double i = m_model->minimumTime(); i <= m_model->maximumTime(); i += timeIncrement)
+ double timeIncrement = timeLabelInterval(painter, maximumTime());
+ for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
paintLabeledTick(i);
painter->restore();
@@ -344,8 +476,8 @@ void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect)
painter->drawText(textRect, Qt::AlignCenter, valueText);
};
- paintLabeledTick(m_scene.minimumValue());
- paintLabeledTick(m_scene.maximumValue());
+ paintLabeledTick(minimumValue());
+ paintLabeledTick(maximumValue());
painter->restore();
}
@@ -358,7 +490,11 @@ double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime)
int nextFactor = 5;
double tickDistance = mapTimeToX(deltaTime);
+
while (true) {
+ if (tickDistance == 0 && deltaTime > maxTime)
+ return maxTime;
+
if (tickDistance > minTextSpacing)
break;
@@ -370,6 +506,7 @@ double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime)
else
nextFactor = 5;
}
+
return deltaTime;
}
diff --git a/src/curveeditor/detail/graphicsview.h b/src/curveeditor/detail/graphicsview.h
index 8d6c5f7..057476b 100644
--- a/src/curveeditor/detail/graphicsview.h
+++ b/src/curveeditor/detail/graphicsview.h
@@ -51,6 +51,34 @@ public:
CurveEditorStyle editorStyle() const;
+ int mapTimeToX(double time) const;
+
+ int mapValueToY(double value) const;
+
+ double mapXtoTime(int x) const;
+
+ double mapYtoValue(int y) const;
+
+ QPointF globalToScene(const QPoint &point) const;
+
+ QPointF globalToRaster(const QPoint &point) const;
+
+ double minimumTime() const;
+
+ double maximumTime() const;
+
+ double minimumValue() const;
+
+ double maximumValue() const;
+
+ QRectF canvasRect() const;
+
+ QRectF timeScaleRect() const;
+
+ QRectF valueScaleRect() const;
+
+ QRectF defaultRasterRect() const;
+
void setStyle(const CurveEditorStyle &style);
void zoomX(double zoom);
@@ -70,6 +98,8 @@ protected:
void mouseReleaseEvent(QMouseEvent *event) override;
+ void wheelEvent(QWheelEvent *event) override;
+
void contextMenuEvent(QContextMenuEvent *event) override;
void drawForeground(QPainter *painter, const QRectF &rect) override;
@@ -77,17 +107,9 @@ protected:
void drawBackground(QPainter *painter, const QRectF &rect) override;
private:
- int mapTimeToX(double time);
-
- int mapValueToY(double value);
+ void scrollContent(double x, double y);
- double mapXtoTime(int x);
-
- double mapYtoValue(int y);
-
- void applyZoom(double x, double y);
-
- void updateSceneRect(const QRectF &rect = QRectF());
+ void applyZoom(double x, double y, const QPoint &pivot = QPoint());
void drawGrid(QPainter *painter, const QRectF &rect);
@@ -117,6 +139,10 @@ private:
double m_zoomY;
QTransform m_transform;
+
+ QPoint m_mouse;
+
+ QPoint m_start;
};
} // End namespace DesignTools.
diff --git a/src/curveeditor/detail/playhead.cpp b/src/curveeditor/detail/playhead.cpp
index b829cf9..15edb4b 100644
--- a/src/curveeditor/detail/playhead.cpp
+++ b/src/curveeditor/detail/playhead.cpp
@@ -27,6 +27,7 @@
#include "curveeditormodel.h"
#include "graphicsview.h"
+#include <QApplication>
#include <QGraphicsScene>
#include <QPainter>
@@ -34,10 +35,26 @@
namespace DesignTools {
-Playhead::Playhead()
+constexpr double g_playheadMargin = 5.0;
+
+Playhead::Playhead(GraphicsView *view)
: m_frame(0)
+ , m_moving(false)
, m_rect()
-{}
+ , m_timer()
+{
+ m_timer.setSingleShot(true);
+ m_timer.setInterval(30);
+ QObject::connect(&m_timer, &QTimer::timeout, view, [this, view]() {
+ if (QApplication::mouseButtons() == Qt::LeftButton)
+ mouseMoveOutOfBounds(view);
+ });
+}
+
+int Playhead::currentFrame() const
+{
+ return m_frame;
+}
void Playhead::moveToFrame(int frame, GraphicsView *view)
{
@@ -51,9 +68,10 @@ void Playhead::resize(GraphicsView *view)
CurveEditorStyle style = view->editorStyle();
- QPointF topLeft = viewRect.topLeft() + QPointF(style.valueAxisWidth, style.timeAxisHeight + 5.);
+ QPointF tl = viewRect.topLeft() + QPointF(style.valueAxisWidth, style.timeAxisHeight - style.playhead.width);
+ QPointF br = viewRect.bottomLeft() + QPointF(style.valueAxisWidth + style.playhead.width, -g_playheadMargin);
- m_rect = QRectF(topLeft, QSizeF(20., viewRect.height() - style.timeAxisHeight - 10.));
+ m_rect = QRectF(tl, br);
moveToFrame(m_frame, view);
}
@@ -63,45 +81,94 @@ bool Playhead::mousePress(const QPointF &pos)
QRectF hitRect = m_rect;
hitRect.setBottom(hitRect.top() + hitRect.width());
- if (hitRect.contains(pos)) {
- m_mouse = pos;
- m_center = m_rect.center().x();
- return true;
- } else {
- m_mouse = QPointF();
- m_center = 0.0;
- }
- return false;
+ m_moving = hitRect.contains(pos);
+
+ return m_moving;
}
bool Playhead::mouseMove(const QPointF &pos, GraphicsView *view)
{
- if (!m_mouse.isNull()) {
- m_center += pos.x() - m_mouse.x();
- m_mouse = pos;
- view->setCurrentFrame(std::round(view->mapXtoTime(m_center)));
- return true;
+ if (m_moving) {
+ CurveEditorStyle style = view->editorStyle();
+
+ QRectF canvas = view->canvasRect().adjusted(0.0, -style.timeAxisHeight, 0.0, 0.0);
+
+ if (canvas.contains(pos))
+ view->setCurrentFrame(std::round(view->mapXtoTime(pos.x())));
+ else if (!m_timer.isActive())
+ m_timer.start();
}
- return false;
+
+ return m_moving;
+}
+
+void Playhead::mouseMoveOutOfBounds(GraphicsView *view)
+{
+ if (QApplication::mouseButtons() != Qt::LeftButton)
+ return;
+
+ CurveEditorStyle style = view->editorStyle();
+
+ QRectF canvas = view->canvasRect();
+
+ QPointF pos = view->globalToScene(QCursor::pos());
+
+ if (pos.x() > canvas.right()) {
+
+ double speed = (pos.x() - canvas.right());
+
+ double nextCenter = m_rect.center().x() + speed;
+
+ double frame = std::round(view->mapXtoTime(nextCenter));
+
+ double framePosition = view->mapTimeToX(frame);
+
+ view->setCurrentFrame(frame);
+
+ double rightSideOut = framePosition + style.playhead.width / 2.0 + style.canvasMargin;
+
+ double overshoot = rightSideOut - canvas.right();
+
+ view->scrollContent(overshoot, 0.0);
+
+ } else if (pos.x() < canvas.left()) {
+
+ double speed = (canvas.left() - pos.x());
+
+ double nextCenter = m_rect.center().x() - speed;
+
+ double frame = std::round(view->mapXtoTime(nextCenter));
+
+ double framePosition = view->mapTimeToX(frame);
+
+ view->setCurrentFrame(frame);
+
+ double leftSideOut = framePosition - style.playhead.width / 2.0 - style.canvasMargin;
+
+ double undershoot = canvas.left() - leftSideOut;
+
+ view->scrollContent(-undershoot, 0.0);
+ }
+
+ m_timer.start();
}
void Playhead::mouseRelease(GraphicsView *view)
{
- if (!m_mouse.isNull())
+ if (m_moving)
emit view->model()->currentFrameChanged(m_frame);
- m_mouse = QPointF();
- m_center = 0.0;
+ m_moving = false;
}
-void Playhead::paint(QPainter *painter) const
+void Playhead::paint(QPainter *painter, GraphicsView *view) const
{
+ CurveEditorStyle style = view->editorStyle();
+
painter->save();
- painter->setPen(Qt::yellow);
+ painter->setPen(style.playhead.color);
painter->setRenderHint(QPainter::Antialiasing, true);
- double radius = 6.0;
-
QRectF rect = m_rect;
rect.setBottom(m_rect.top() + m_rect.width());
@@ -111,20 +178,20 @@ void Playhead::paint(QPainter *painter) const
QPointF left(rect.left(), rect.top());
QLineF rightToBottom(right, bottom);
- rightToBottom.setLength(radius);
+ rightToBottom.setLength(style.playhead.radius);
QLineF leftToBottom(left, bottom);
- leftToBottom.setLength(radius);
+ leftToBottom.setLength(style.playhead.radius);
QPainterPath path(top);
- path.lineTo(right - QPointF(radius, 0.));
+ path.lineTo(right - QPointF(style.playhead.radius, 0.));
path.quadTo(right, rightToBottom.p2());
path.lineTo(bottom);
path.lineTo(leftToBottom.p2());
- path.quadTo(left, left + QPointF(radius, 0.));
+ path.quadTo(left, left + QPointF(style.playhead.radius, 0.));
path.closeSubpath();
- painter->fillPath(path, Qt::yellow);
+ painter->fillPath(path, style.playhead.color);
painter->drawLine(top + QPointF(0., 5.), QPointF(m_rect.center().x(), m_rect.bottom()));
diff --git a/src/curveeditor/detail/playhead.h b/src/curveeditor/detail/playhead.h
index f82a04d..ffb4da4 100644
--- a/src/curveeditor/detail/playhead.h
+++ b/src/curveeditor/detail/playhead.h
@@ -25,7 +25,10 @@
#pragma once
-#include <QGraphicsObject>
+#include <QRectF>
+#include <QTimer>
+
+class QPainter;
namespace DesignTools {
@@ -33,12 +36,12 @@ class GraphicsView;
class Playhead
{
- friend class GraphicsView;
-
public:
- Playhead();
+ Playhead(GraphicsView *view);
+
+ int currentFrame() const;
- void paint(QPainter *painter) const;
+ void paint(QPainter *painter, GraphicsView *view) const;
void moveToFrame(int frame, GraphicsView *view);
@@ -51,13 +54,17 @@ public:
void mouseRelease(GraphicsView *view);
private:
+ void mouseMoveOutOfBounds(GraphicsView *view);
+
int m_frame;
- double m_center;
+ bool m_moving;
QRectF m_rect;
- QPointF m_mouse;
+ QTimer m_timer;
+
+ GraphicsView *m_view;
};
} // End namespace DesignTools.