diff options
Diffstat (limited to 'src/curveeditor/detail/graphicsview.cpp')
-rw-r--r-- | src/curveeditor/detail/graphicsview.cpp | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/src/curveeditor/detail/graphicsview.cpp b/src/curveeditor/detail/graphicsview.cpp new file mode 100644 index 0000000..e6dfb11 --- /dev/null +++ b/src/curveeditor/detail/graphicsview.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Tooling +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** 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 <QMenu> +#include <QResizeEvent> + +#include <cmath> + +namespace DesignTools { + +GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent) + : QGraphicsView(parent) + , m_scene() + , m_style(model->style()) + , m_model(model) + , m_dialog(m_style, this) + , m_zoomX(0.0) + , m_zoomY(0.0) +{ + setScene(&m_scene); + setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + + connect(&m_dialog, &CurveEditorStyleDialog::styleChanged, this, &GraphicsView::setStyle); + + auto itemSlot = [this](unsigned int id, const AnimationCurve &curve) { + applyZoom(m_zoomX, m_zoomY); + m_model->setCurve(id, curve); + }; + + connect(&m_scene, &GraphicsScene::curveChanged, itemSlot); + + applyZoom(m_zoomX, m_zoomY); + update(); +} + +void GraphicsView::setStyle(const CurveEditorStyle &style) +{ + m_style = style; + + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) + curveItem->setStyle(style); + } + + applyZoom(m_zoomX, m_zoomY); + viewport()->update(); +} + +void GraphicsView::zoomX(double zoom) +{ + applyZoom(zoom, m_zoomY); + viewport()->update(); +} + +void GraphicsView::zoomY(double zoom) +{ + applyZoom(m_zoomX, zoom); + viewport()->update(); +} + +void GraphicsView::reset(const std::vector<CurveItem *> &items) +{ + m_scene.clear(); + for (auto *item : items) { + m_scene.addCurveItem(item); + } + + applyZoom(m_zoomX, m_zoomY); + viewport()->update(); +} + +void GraphicsView::resizeEvent(QResizeEvent *event) +{ + applyZoom(m_zoomX, m_zoomY); + QGraphicsView::resizeEvent(event); +} + +void GraphicsView::contextMenuEvent(QContextMenuEvent *event) +{ + Q_UNUSED(event); + + auto openStyleEditor = [this]() { m_dialog.show(); }; + + QMenu menu; + QAction *openEditorAction = menu.addAction(tr("Open Style Editor")); + connect(openEditorAction, &QAction::triggered, openStyleEditor); + + menu.exec(event->globalPos()); +} + +void GraphicsView::drawForeground(QPainter *painter, const QRectF &rect) +{ + auto gap = QRectF(rect.topLeft(), QSizeF(m_style.valueAxisWidth, m_style.timeAxisHeight)); + + auto abscissa = QRectF(gap.topRight(), rect.topRight() + QPointF(0.0, gap.height())); + if (abscissa.isValid()) + drawTimeScale(painter, abscissa); + + auto ordinate = QRectF(gap.bottomLeft(), rect.bottomLeft() + QPointF(gap.width(), 0.0)); + if (ordinate.isValid()) + drawValueScale(painter, ordinate); + + painter->fillRect(gap, m_style.backgroundAlternateBrush); +} + +void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect) +{ + painter->fillRect(rect, m_style.backgroundBrush); + painter->fillRect(scene()->sceneRect(), m_style.backgroundAlternateBrush); + + drawGrid(painter, rect); + drawExtremaX(painter, rect); + drawExtremaY(painter, rect); +} + +int GraphicsView::mapTimeToX(double time) +{ + return std::round(time * scaleX(m_transform)); +} + +int GraphicsView::mapValueToY(double y) +{ + return std::round(y * scaleY(m_transform)); +} + +double GraphicsView::mapXtoTime(int x) +{ + return static_cast<double>(x) / scaleX(m_transform); +} + +double GraphicsView::mapYtoValue(int y) +{ + return static_cast<double>(y) / scaleY(m_transform); +} + +void GraphicsView::applyZoom(double x, double y) +{ + m_zoomX = x; + m_zoomY = y; + + double canvasWidth = rect().width() - m_style.valueAxisWidth - 2 * m_style.canvasMargin - rect().width() / 20.0; + double xZoomedOut = canvasWidth / (m_model->maximumTime() - m_model->minimumTime()); + 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 yZoomedIn = m_style.zoomInHeight; + double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn); + + m_transform = QTransform::fromScale(scaleX, scaleY); + + QRectF sceneBounds; + for (auto *item : items()) + if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) + sceneBounds = sceneBounds.united(curveItem->setComponentTransform(m_transform)); + + updateSceneRect(sceneBounds); +} + +void GraphicsView::updateSceneRect(const QRectF &rect) +{ + if (rect.isValid()) + scene()->setSceneRect(rect); + + QRectF sr = scene()->sceneRect().adjusted( + -m_style.valueAxisWidth - m_style.canvasMargin, + -m_style.timeAxisHeight - m_style.canvasMargin, + m_style.canvasMargin, + m_style.canvasMargin); + + setSceneRect(sr); +} + +void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect) +{ + QRectF gridRect = rect.adjusted( + m_style.valueAxisWidth + m_style.canvasMargin, + m_style.timeAxisHeight + m_style.canvasMargin, + -m_style.canvasMargin, + -m_style.canvasMargin); + + if (!gridRect.isValid()) + return; + + auto drawVerticalLine = [painter, gridRect](double position) { + painter->drawLine(position, gridRect.top(), position, gridRect.bottom()); + }; + + painter->save(); + painter->setPen(m_style.gridColor); + + double timeIncrement = timeLabelInterval(painter, m_model->maximumTime()); + for (double i = m_model->minimumTime(); i <= m_model->maximumTime(); i += timeIncrement) + drawVerticalLine(mapTimeToX(i)); + + painter->restore(); +} + +void GraphicsView::drawExtremaX(QPainter *painter, const QRectF &rect) +{ + auto drawVerticalLine = [rect, painter](double position) { + painter->drawLine(position, rect.top(), position, rect.bottom()); + }; + + painter->save(); + painter->setPen(Qt::red); + drawVerticalLine(mapTimeToX(m_model->minimumTime())); + drawVerticalLine(mapTimeToX(m_model->maximumTime())); + painter->restore(); +} + +void GraphicsView::drawExtremaY(QPainter *painter, const QRectF &rect) +{ + auto drawHorizontalLine = [rect, painter](double position) { + painter->drawLine(rect.left(), position, rect.right(), position); + }; + + painter->save(); + painter->setPen(Qt::blue); + drawHorizontalLine(mapValueToY(m_scene.minimumValue())); + drawHorizontalLine(mapValueToY(m_scene.maximumValue())); + + drawHorizontalLine(mapValueToY(0.0)); + + painter->restore(); +} + +void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect) +{ + painter->save(); + painter->setPen(m_style.fontColor); + painter->fillRect(rect, m_style.backgroundAlternateBrush); + + QFontMetrics fm(painter->font()); + + auto paintLabeledTick = [this, painter, rect, fm](double time) { + QString timeText = QString("%1").arg(time); + + int position = mapTimeToX(time); + + 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) + paintLabeledTick(i); + + painter->restore(); +} + +void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect) +{ + painter->save(); + painter->setPen(m_style.fontColor); + painter->fillRect(rect, m_style.backgroundAlternateBrush); + + QFontMetrics fm(painter->font()); + auto paintLabeledTick = [this, painter, rect, fm](double value) { + QString valueText = QString("%1").arg(value); + + int position = mapValueToY(value); + + QRect textRect = fm.boundingRect(valueText); + textRect.moveCenter(QPoint(rect.center().x(), position)); + painter->drawText(textRect, Qt::AlignCenter, valueText); + }; + + paintLabeledTick(m_scene.minimumValue()); + paintLabeledTick(m_scene.maximumValue()); + painter->restore(); +} + +double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime) +{ + QFontMetrics fm(painter->font()); + int minTextSpacing = fm.width(QString("X%1X").arg(maxTime)); + + int deltaTime = 1; + int nextFactor = 5; + + double tickDistance = mapTimeToX(deltaTime); + while (true) { + if (tickDistance > minTextSpacing) + break; + + deltaTime *= nextFactor; + tickDistance = mapTimeToX(deltaTime); + + if (nextFactor == 5) + nextFactor = 2; + else + nextFactor = 5; + } + return deltaTime; +} + +} // End namespace DesignTools. |