diff options
Diffstat (limited to 'src')
-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 |
10 files changed, 437 insertions, 124 deletions
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. |