diff options
author | Knud Dollereder <knud.dollereder@qt.io> | 2019-03-27 15:13:11 +0100 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2019-03-28 08:53:24 +0000 |
commit | df6afb91041d5225448a13aaf3bbb6f11137b0c0 (patch) | |
tree | 3809a1686ce9c5eed9697a8d868a49225813c6e0 | |
parent | c9807788db6219287ab1bff001fde7592171d7a8 (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.cpp | 3 | ||||
-rw-r--r-- | src/curveeditor/animationcurve.cpp | 13 | ||||
-rw-r--r-- | src/curveeditor/curveeditorstyle.h | 36 | ||||
-rw-r--r-- | src/curveeditor/detail/curveeditorstyledialog.cpp | 30 | ||||
-rw-r--r-- | src/curveeditor/detail/curveeditorstyledialog.h | 12 | ||||
-rw-r--r-- | src/curveeditor/detail/graphicsscene.cpp | 34 | ||||
-rw-r--r-- | src/curveeditor/detail/graphicsscene.h | 7 | ||||
-rw-r--r-- | src/curveeditor/detail/graphicsview.cpp | 233 | ||||
-rw-r--r-- | src/curveeditor/detail/graphicsview.h | 46 | ||||
-rw-r--r-- | src/curveeditor/detail/playhead.cpp | 129 | ||||
-rw-r--r-- | src/curveeditor/detail/playhead.h | 21 |
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. |