diff options
47 files changed, 2283 insertions, 283 deletions
diff --git a/examples/curveeditorapp/curveeditorapp.pro b/examples/curveeditorapp/curveeditorapp.pro index 2602ffd..3f44741 100644 --- a/examples/curveeditorapp/curveeditorapp.pro +++ b/examples/curveeditorapp/curveeditorapp.pro @@ -10,11 +10,14 @@ DEFINES += QT_DEPRECATED_WARNINGS INCLUDEPATH += . +RESOURCES = $$PWD/../../resources/curveeditor.qrc + include($$PWD/../../sharedcomponents.pri) # Input HEADERS += \ - mainwindow.h + mainwindow.h \ + examplecurvemodel.h SOURCES += \ main.cpp \ diff --git a/examples/curveeditorapp/examplecurvemodel.cpp b/examples/curveeditorapp/examplecurvemodel.cpp index a20740b..751ae1c 100644 --- a/examples/curveeditorapp/examplecurvemodel.cpp +++ b/examples/curveeditorapp/examplecurvemodel.cpp @@ -24,9 +24,18 @@ ****************************************************************************/ #include "examplecurvemodel.h" #include "animationcurve.h" +#include "curveeditorstyle.h" + +#include <QDebug> namespace DesignTools { +ExampleCurveModel::ExampleCurveModel() + : CurveEditorModel() +{ + connect(this, &CurveEditorModel::curveChanged, this, &ExampleCurveModel::curveChangeEmitted); +} + double ExampleCurveModel::minimumTime() const { return 0.0; @@ -37,64 +46,75 @@ double ExampleCurveModel::maximumTime() const return 500.0; } -double ExampleCurveModel::minimumValue() const -{ - return -1.0; -} - -double ExampleCurveModel::maximumValue() const -{ - return 1.0; -} - -AnimationCurve ExampleCurveModel::curve(int i) const -{ - if (i == 0) { - return AnimationCurve({ - Keyframe(QPointF(0.0, -1.0), QPointF(), QPointF(100.0, -1.0)), - Keyframe(QPointF(250.0, 0.0), QPointF(150.0, 0.0), QPointF(350.0, 0.0)), - Keyframe(QPointF(500.0, 1.0), QPointF(400.0, 1.0), QPointF()), - }); - } else { - return AnimationCurve({ - Keyframe(QPointF(0.0, -1.0), QPointF(), QPointF(1.0, -1.0)), - Keyframe(QPointF(5.0, 0.0), QPointF(4.0, 0.0), QPointF(6.0, 0.0)), - Keyframe(QPointF(200.0, 1.0), QPointF(199.0, 1.0), QPointF()), - }); - } -} - CurveEditorStyle ExampleCurveModel::style() const { // Pseudo auto generated. See: CurveEditorStyleDialog CurveEditorStyle out; - out.backgroundBrush = QBrush(QColor(5, 0, 100)); + out.backgroundBrush = QBrush(QColor(55, 55, 55)); out.backgroundAlternateBrush = QBrush(QColor(0, 0, 50)); - out.fontColor = QColor(200, 200, 200); - out.gridColor = QColor(128, 128, 128); + out.fontColor = QColor(255, 255, 255); + out.gridColor = QColor(114, 116, 118); out.canvasMargin = 5; out.zoomInWidth = 99; out.zoomInHeight = 99; out.timeAxisHeight = 40; out.timeOffsetLeft = 10; out.timeOffsetRight = 10; - out.rangeBarColor = QColor(50, 50, 255); + out.rangeBarColor = QColor(46, 47, 48); out.rangeBarCapsColor = QColor(50, 50, 255); out.valueAxisWidth = 60; out.valueOffsetTop = 10; out.valueOffsetBottom = 10; - out.playheadColor = QColor(200, 200, 0); + out.playheadColor = QColor(255, 255, 0); out.handleStyle.size = 12; out.handleStyle.lineWidth = 1; - out.handleStyle.color = QColor(200, 0, 0); - out.handleStyle.selectionColor = QColor(200, 200, 200); + out.handleStyle.color = QColor(255, 255, 255); + out.handleStyle.selectionColor = QColor(255, 255, 255); out.keyframeStyle.size = 13; - out.keyframeStyle.color = QColor(240, 255, 0); - out.keyframeStyle.selectionColor = QColor(200, 200, 200); + out.keyframeStyle.color = QColor(172, 210, 255); + out.keyframeStyle.selectionColor = QColor(255, 255, 255); out.curveStyle.width = 1; out.curveStyle.color = QColor(0, 200, 0); - out.curveStyle.selectionColor = QColor(200, 200, 200); + out.curveStyle.selectionColor = QColor(255, 255, 255); return out; } +void ExampleCurveModel::createItems() +{ + AnimationCurve curve1({ + Keyframe(QPointF(0.0, -1.0), QPointF(), QPointF(100.0, -1.0)), + Keyframe(QPointF(150.0, 0.7), QPointF(50.0, 0.7), QPointF(250.0, 0.7)), + Keyframe(QPointF(500.0, 1.0), QPointF(400.0, 1.0), QPointF())}); + + AnimationCurve curve2({ + Keyframe(QPointF(0.0, -1.0), QPointF(), QPointF(100.0, -1.0)), + Keyframe(QPointF(200.0, 0.4), QPointF(100.0, 0.4), QPointF(300.0, 0.4)), + Keyframe(QPointF(500.0, 1.0), QPointF(400.0, 1.0), QPointF())}); + + AnimationCurve curve3({ + Keyframe(QPointF(0.0, -1.0), QPointF(), QPointF(100.0, -1.0)), + Keyframe(QPointF(250.0, 0.0), QPointF(150.0, 0.0), QPointF(350.0, 0.0)), + Keyframe(QPointF(500.0, 1.0), QPointF(400.0, 1.0), QPointF())}); + + AnimationCurve curve4({ + Keyframe(QPointF(0.0, -1.0), QPointF(), QPointF(100.0, -1.0)), + Keyframe(QPointF(300.0, -0.4), QPointF(200.0, -0.4), QPointF(400.0, -0.4)), + Keyframe(QPointF(500.0, 1.0), QPointF(400.0, 1.0), QPointF())}); + + auto *instance = new NodeTreeItem("Instance", QIcon(":/ICON_INSTANCE")); + instance->addChild(new PropertyTreeItem("Position X", curve1)); + instance->addChild(new PropertyTreeItem("Position Y", curve2)); + + auto *material = new NodeTreeItem("Material", QIcon(":/ICON_MATERIAL")); + material->addChild(new PropertyTreeItem("Opacity", curve3)); + material->addChild(new PropertyTreeItem("Color R", curve4)); + + reset({instance, material}); +} + +void ExampleCurveModel::curveChangeEmitted(PropertyTreeItem *item) +{ + qDebug() << "The Curve with the id " << item->id() << " has changed"; +} + } // End namespace DesignTools. diff --git a/examples/curveeditorapp/examplecurvemodel.h b/examples/curveeditorapp/examplecurvemodel.h index 7f0433e..815af5e 100644 --- a/examples/curveeditorapp/examplecurvemodel.h +++ b/examples/curveeditorapp/examplecurvemodel.h @@ -31,18 +31,20 @@ namespace DesignTools { class ExampleCurveModel : public CurveEditorModel { + Q_OBJECT + public: + ExampleCurveModel(); + double minimumTime() const override; double maximumTime() const override; - double minimumValue() const override; - - double maximumValue() const override; + CurveEditorStyle style() const override; - AnimationCurve curve(int i) const override; + void createItems(); - CurveEditorStyle style() const override; + void curveChangeEmitted(PropertyTreeItem *item); }; } // End namespace DesignTools. diff --git a/examples/curveeditorapp/mainwindow.cpp b/examples/curveeditorapp/mainwindow.cpp index 7138b4e..6fbcd67 100644 --- a/examples/curveeditorapp/mainwindow.cpp +++ b/examples/curveeditorapp/mainwindow.cpp @@ -24,7 +24,6 @@ ****************************************************************************/ #include "mainwindow.h" #include "curveeditor.h" -#include "curveeditorview.h" #include "examplecurvemodel.h" #include <QHBoxLayout> @@ -58,8 +57,8 @@ MainWindow::MainWindow() auto *model = new ExampleCurveModel; auto *editor = new CurveEditor(model); - auto zoomX = [editor](int val) { editor->view()->zoomX(static_cast<double>(val) / 1000.); }; - auto zoomY = [editor](int val) { editor->view()->zoomY(static_cast<double>(val) / 1000.); }; + auto zoomX = [editor](int val) { editor->zoomX(static_cast<double>(val) / 1000.); }; + auto zoomY = [editor](int val) { editor->zoomY(static_cast<double>(val) / 1000.); }; auto *box = new QVBoxLayout; box->addWidget(editor); @@ -71,6 +70,8 @@ MainWindow::MainWindow() setCentralWidget(centralWidget); resize(QSize(1070, 739)); + + model->createItems(); } } // End namespace DesignTools. diff --git a/resources/curveeditor.qrc b/resources/curveeditor.qrc new file mode 100644 index 0000000..9dfc5d7 --- /dev/null +++ b/resources/curveeditor.qrc @@ -0,0 +1,14 @@ + <!DOCTYPE RCC><RCC version="1.0"> + <qresource> + + <file alias = "ICON_MATERIAL" >material.png</file> + <file alias = "ICON_INSTANCE" >instance.png</file> + + <file alias = "ICON_LOCKED" >locked.png</file> + <file alias = "ICON_UNLOCKED" >unlocked.png</file> + + <file alias = "ICON_PINNED" >pinned.png</file> + <file alias = "ICON_UNPINNED" >unpinned.png</file> + + </qresource> + </RCC> diff --git a/resources/instance.png b/resources/instance.png Binary files differnew file mode 100644 index 0000000..4f3d80a --- /dev/null +++ b/resources/instance.png diff --git a/resources/locked.png b/resources/locked.png Binary files differnew file mode 100644 index 0000000..1e8d129 --- /dev/null +++ b/resources/locked.png diff --git a/resources/material.png b/resources/material.png Binary files differnew file mode 100644 index 0000000..22524e8 --- /dev/null +++ b/resources/material.png diff --git a/resources/pinned.png b/resources/pinned.png Binary files differnew file mode 100644 index 0000000..8f70e76 --- /dev/null +++ b/resources/pinned.png diff --git a/resources/unlocked.png b/resources/unlocked.png Binary files differnew file mode 100644 index 0000000..1e8d129 --- /dev/null +++ b/resources/unlocked.png diff --git a/resources/unpinned.png b/resources/unpinned.png Binary files differnew file mode 100644 index 0000000..8f70e76 --- /dev/null +++ b/resources/unpinned.png diff --git a/src/curveeditor/animationcurve.cpp b/src/curveeditor/animationcurve.cpp index 4818a81..acb64ce 100644 --- a/src/curveeditor/animationcurve.cpp +++ b/src/curveeditor/animationcurve.cpp @@ -24,120 +24,155 @@ ****************************************************************************/ #include "animationcurve.h" +#include "detail/curvesegment.h" -#include <assert.h> -#include <cmath> +#include <QLineF> namespace DesignTools { -Keyframe::Keyframe() - : m_position() - , m_leftHandle() - , m_rightHandle() +AnimationCurve::AnimationCurve() + : m_frames() {} -Keyframe::Keyframe(const QPointF &position) - : m_position(position) - , m_leftHandle() - , m_rightHandle() -{} +AnimationCurve::AnimationCurve(const std::vector<Keyframe> &frames) + : m_frames(frames) + , m_minY(std::numeric_limits<double>::max()) + , m_maxY(std::numeric_limits<double>::lowest()) +{ + if (m_frames.size() >= 2) { + for (auto e : extrema()) { -Keyframe::Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle) - : m_position(position) - , m_leftHandle(leftHandle) - , m_rightHandle(rightHandle) -{} + if (m_minY > e.y()) + m_minY = e.y(); -bool Keyframe::hasLeftHandle() const -{ - return !m_leftHandle.isNull(); + if (m_maxY < e.y()) + m_maxY = e.y(); + } + } } -bool Keyframe::hasRightHandle() const +bool AnimationCurve::isValid() const { - return !m_rightHandle.isNull(); + return !m_frames.empty(); } -QPointF Keyframe::position() const +double AnimationCurve::minimumTime() const { - return m_position; + if (!m_frames.empty()) + return m_frames.front().position().x(); + + return std::numeric_limits<double>::max(); } -QPointF Keyframe::leftHandle() const +double AnimationCurve::maximumTime() const { - return m_leftHandle; + if (!m_frames.empty()) + return m_frames.back().position().x(); + + return std::numeric_limits<double>::lowest(); } -QPointF Keyframe::rightHandle() const +double AnimationCurve::minimumValue() const { - return m_rightHandle; + return m_minY; } -void Keyframe::setPosition(const QPointF &pos) +double AnimationCurve::maximumValue() const { - m_position = pos; + return m_maxY; } -void Keyframe::setLeftHandle(const QPointF &pos) +std::vector<Keyframe> AnimationCurve::keyframes() const { - m_leftHandle = pos; + return m_frames; } -void Keyframe::setRightHandle(const QPointF &pos) +std::vector<QPointF> AnimationCurve::extrema() const { - m_rightHandle = pos; -} + std::vector<QPointF> out; + CurveSegment segment; + segment.setLeft(m_frames.at(0)); -CurveSegment::CurveSegment() - : m_left() - , m_right() -{} + for (size_t i = 1; i < m_frames.size(); ++i) { -CurveSegment::CurveSegment(const Keyframe &left, const Keyframe &right) - : m_left(left) - , m_right(right) -{} + segment.setRight(m_frames[i]); + + const auto es = segment.extrema(); + out.insert(std::end(out), std::begin(es), std::end(es)); + + segment.setLeft(m_frames[i]); + } + + return out; +} -QPointF CurveSegment::evaluate(double t) const +std::vector<double> AnimationCurve::yForX(double x) const { - assert(t >= 0. && t <= 1.); + if (m_frames.front().position().x() > x) + return std::vector<double>(); + + CurveSegment segment; + for (auto &frame : m_frames) { + if (frame.position().x() > x) { + segment.setRight(frame); + return segment.yForX(x); + } + segment.setLeft(frame); + } + return std::vector<double>(); +} - const double it = 1.0 - t; +std::vector<double> AnimationCurve::xForY(double y, uint segment) const +{ + if (m_frames.size() > segment + 1) { + CurveSegment seg(m_frames[segment], m_frames[segment + 1]); + return seg.xForY(y); + } + return std::vector<double>(); +} - auto binomialPolynomial = [t, it](double p0, double p1, double p2, double p3) { - return p0 * std::pow(it, 3.0) + p1 * 3.0 * std::pow(it, 2.0) * t - + p2 * 3.0 * it * std::pow(t, 2.0) + p3 * std::pow(t, 3.0); - }; +bool AnimationCurve::intersects(const QPointF &coord, double radius) +{ + if (m_frames.size() < 2) + return false; - const double x = binomialPolynomial( - m_left.position().x(), m_left.rightHandle().x(), - m_right.leftHandle().x(), m_right.position().x()); + std::vector<CurveSegment> influencer; - const double y = binomialPolynomial( - m_left.position().y(), m_left.rightHandle().y(), - m_right.leftHandle().y(), m_right.position().y()); + CurveSegment current; + current.setLeft(m_frames.at(0)); - return QPointF(x, y); -} + for (size_t i = 1; i < m_frames.size(); ++i) { + Keyframe &frame = m_frames.at(i); + current.setRight(frame); -AnimationCurve::AnimationCurve() - : m_frames() -{} + if (current.containsX(coord.x() - radius) || + current.containsX(coord.x()) || + current.containsX(coord.x() + radius)) { + influencer.push_back(current); + } -AnimationCurve::AnimationCurve(const std::vector<Keyframe> &frames) - : m_frames(frames) -{} + if (frame.position().x() > coord.x() + radius) + break; -bool AnimationCurve::isValid() const -{ - return !m_frames.empty(); -} + current.setLeft(frame); + } -std::vector<Keyframe> AnimationCurve::keyframes() const -{ - return m_frames; + for (auto &segment : influencer) { + for (auto &y : segment.yForX(coord.x())) { + QLineF line(coord.x(), y, coord.x(), coord.y()); + if (line.length() < radius) + return true; + } + + for (auto &x : segment.xForY(coord.y())) { + QLineF line(x, coord.y(), coord.x(), coord.y()); + if (line.length() < radius) + return true; + } + } + return false; } } // End namespace DesignTools. diff --git a/src/curveeditor/animationcurve.h b/src/curveeditor/animationcurve.h index 7b42e07..0533e47 100644 --- a/src/curveeditor/animationcurve.h +++ b/src/curveeditor/animationcurve.h @@ -25,75 +25,45 @@ #pragma once -#include <QPointF> +#include "keyframe.h" #include <vector> namespace DesignTools { -class Keyframe +class AnimationCurve { public: - Keyframe(); - - Keyframe(const QPointF &position); - - Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle); - - bool hasLeftHandle() const; - - bool hasRightHandle() const; - - QPointF position() const; - - QPointF leftHandle() const; + AnimationCurve(); - QPointF rightHandle() const; + AnimationCurve(const std::vector<Keyframe> &frames); - void setPosition(const QPointF &pos); + bool isValid() const; - void setLeftHandle(const QPointF &pos); + double minimumTime() const; - void setRightHandle(const QPointF &pos); + double maximumTime() const; -private: - QPointF m_position = QPointF(); + double minimumValue() const; - QPointF m_leftHandle = QPointF(); + double maximumValue() const; - QPointF m_rightHandle = QPointF(); -}; + std::vector<Keyframe> keyframes() const; + std::vector<QPointF> extrema() const; -class CurveSegment -{ -public: - CurveSegment(); + std::vector<double> yForX(double x) const; - CurveSegment(const Keyframe &first, const Keyframe &last); + std::vector<double> xForY(double y, uint segment) const; - QPointF evaluate(double t) const; + bool intersects(const QPointF &coord, double radius); private: - Keyframe m_left; - - Keyframe m_right; -}; - - -class AnimationCurve -{ -public: - AnimationCurve(); - - AnimationCurve(const std::vector<Keyframe> &frames); + std::vector<Keyframe> m_frames; - bool isValid() const; + double m_minY; - std::vector<Keyframe> keyframes() const; - -private: - std::vector<Keyframe> m_frames; + double m_maxY; }; } // End namespace DesignTools. diff --git a/src/curveeditor/curveeditor.cpp b/src/curveeditor/curveeditor.cpp index b72f905..8501041 100644 --- a/src/curveeditor/curveeditor.cpp +++ b/src/curveeditor/curveeditor.cpp @@ -22,36 +22,43 @@ ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ + #include "curveeditor.h" #include "curveeditormodel.h" -#include "curveeditorview.h" +#include "detail/curveitem.h" +#include "detail/graphicsview.h" +#include "detail/treeview.h" #include <QHBoxLayout> #include <QSplitter> -#include <QTreeView> namespace DesignTools { CurveEditor::CurveEditor(CurveEditorModel *model, QWidget *parent) : QWidget(parent) - , m_tree(new QTreeView) - , m_view(new CurveEditorView(model)) + , m_tree(new TreeView(model, this)) + , m_view(new GraphicsView(model)) { - m_tree->setFixedWidth(100); - QSplitter *splitter = new QSplitter; splitter->addWidget(m_tree); splitter->addWidget(m_view); - splitter->setSizes({5, 80}); + splitter->setStretchFactor(1, 2); QHBoxLayout *box = new QHBoxLayout; box->addWidget(splitter); setLayout(box); + + connect(m_tree, &TreeView::curvesSelected, m_view, &GraphicsView::reset); +} + +void CurveEditor::zoomX(double zoom) +{ + m_view->zoomX(zoom); } -CurveEditorView *CurveEditor::view() const +void CurveEditor::zoomY(double zoom) { - return m_view; + m_view->zoomY(zoom); } } // End namespace DesignTools. diff --git a/src/curveeditor/curveeditor.h b/src/curveeditor/curveeditor.h index f5e1af8..a2c5873 100644 --- a/src/curveeditor/curveeditor.h +++ b/src/curveeditor/curveeditor.h @@ -27,12 +27,11 @@ #include <QWidget> -class QTreeView; - namespace DesignTools { class CurveEditorModel; -class CurveEditorView; +class GraphicsView; +class TreeView; class CurveEditor : public QWidget { @@ -41,12 +40,14 @@ class CurveEditor : public QWidget public: CurveEditor(CurveEditorModel *model, QWidget *parent = nullptr); - CurveEditorView *view() const; + void zoomX(double zoom); + + void zoomY(double zoom); private: - QTreeView *m_tree; + TreeView *m_tree; - CurveEditorView *m_view; + GraphicsView *m_view; }; } // End namespace DesignTools. diff --git a/src/curveeditor/curveeditor.pri b/src/curveeditor/curveeditor.pri index 861abd8..6b325e3 100644 --- a/src/curveeditor/curveeditor.pri +++ b/src/curveeditor/curveeditor.pri @@ -1,23 +1,38 @@ INCLUDEPATH += $$PWD -### include required files -HEADERS += \ +HEADERS += \ $$PWD/animationcurve.h \ $$PWD/curveeditor.h \ - $$PWD/curveeditorstyledialog.h \ - $$PWD/curveeditorview.h \ - $$PWD/curveitem.h \ - $$PWD/colorcontrol.h \ - $$PWD/handleitem.h \ - $$PWD/keyframeitem.h + $$PWD/curveeditormodel.h \ + $$PWD/keyframe.h \ + $$PWD/detail/colorcontrol.h \ + $$PWD/detail/curveeditorstyledialog.h \ + $$PWD/detail/curveitem.h \ + $$PWD/detail/curvesegment.h \ + $$PWD/detail/graphicsscene.h \ + $$PWD/detail/graphicsview.h \ + $$PWD/detail/handleitem.h \ + $$PWD/detail/keyframeitem.h \ + $$PWD/detail/treeitem.h \ + $$PWD/detail/treeitemdelegate.h \ + $$PWD/detail/treemodel.h \ + $$PWD/detail/treeview.h -SOURCES += \ +SOURCES += \ $$PWD/animationcurve.cpp \ $$PWD/curveeditor.cpp \ - $$PWD/curveeditorstyledialog.cpp \ - $$PWD/curveeditorview.cpp \ - $$PWD/curveitem.cpp \ - $$PWD/colorcontrol.cpp \ - $$PWD/handleitem.cpp \ - $$PWD/keyframeitem.cpp \ - $$PWD/utils.cpp + $$PWD/curveeditormodel.cpp \ + $$PWD/keyframe.cpp \ + $$PWD/detail/colorcontrol.cpp \ + $$PWD/detail/curveeditorstyledialog.cpp \ + $$PWD/detail/curveitem.cpp \ + $$PWD/detail/curvesegment.cpp \ + $$PWD/detail/graphicsscene.cpp \ + $$PWD/detail/graphicsview.cpp \ + $$PWD/detail/handleitem.cpp \ + $$PWD/detail/keyframeitem.cpp \ + $$PWD/detail/treeitem.cpp \ + $$PWD/detail/treeitemdelegate.cpp \ + $$PWD/detail/treemodel.cpp \ + $$PWD/detail/treeview.cpp \ + $$PWD/detail/utils.cpp diff --git a/src/curveeditor/curveeditormodel.cpp b/src/curveeditor/curveeditormodel.cpp new file mode 100644 index 0000000..31d4d6b --- /dev/null +++ b/src/curveeditor/curveeditormodel.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 "curveeditormodel.h" + +namespace DesignTools { + +CurveEditorModel::CurveEditorModel(QObject *parent) + : TreeModel(parent) +{} + +CurveEditorModel::~CurveEditorModel() {} + +void CurveEditorModel::setCurrentTime(uint frame) +{ + Q_UNUSED(frame); + printf("CurveEditorModel::setCurrentTime unimplememted!\n"); +} + +void CurveEditorModel::setCurve(unsigned int id, const AnimationCurve &curve) +{ + if (TreeItem *item = find(id)) { + if (PropertyTreeItem *propertyItem = item->asPropertyItem()) { + propertyItem->setCurve(curve); + emit curveChanged(propertyItem); + } + } +} + +void CurveEditorModel::reset(const std::vector<TreeItem *> &items) +{ + beginResetModel(); + + initialize(); + + unsigned int counter = 0; + for (auto *item : items) { + item->setId(++counter); + root()->addChild(item); + } + + endResetModel(); +} + + +NodeTreeItem::NodeTreeItem(const QString &name, const QIcon &icon) + : TreeItem(name) + , m_icon(icon) +{ + Q_UNUSED(icon); +} + +NodeTreeItem *NodeTreeItem::asNodeItem() +{ + return this; +} + +QIcon NodeTreeItem::icon() const +{ + return m_icon; +} + + +PropertyTreeItem::PropertyTreeItem(const QString &name, const AnimationCurve &curve) + : TreeItem(name) + , m_curve(curve) +{} + +PropertyTreeItem *PropertyTreeItem::asPropertyItem() +{ + return this; +} + +AnimationCurve PropertyTreeItem::curve() const +{ + return m_curve; +} + +void PropertyTreeItem::setCurve(const AnimationCurve &curve) +{ + m_curve = curve; +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/curveeditormodel.h b/src/curveeditor/curveeditormodel.h index 351aa73..5c0bb4e 100644 --- a/src/curveeditor/curveeditormodel.h +++ b/src/curveeditor/curveeditormodel.h @@ -25,7 +25,10 @@ #pragma once -#include "curveeditorstyle.h" +#include "detail/treeitem.h" +#include "detail/treemodel.h" + +#include <QIcon> #include <vector> @@ -33,22 +36,66 @@ class QPointF; namespace DesignTools { +struct CurveEditorStyle; + class AnimationCurve; -class CurveEditorModel +class CurveEditorModel : public TreeModel { + Q_OBJECT + +signals: + void curveChanged(PropertyTreeItem *item); + public: virtual double minimumTime() const = 0; virtual double maximumTime() const = 0; - virtual double minimumValue() const = 0; + virtual CurveEditorStyle style() const = 0; + +public: + CurveEditorModel(QObject *parent = nullptr); + + ~CurveEditorModel() override; + + void setCurrentTime(uint frame); + + void setCurve(unsigned int id, const AnimationCurve &curve); + + void reset(const std::vector<TreeItem *> &items); +}; + + +class NodeTreeItem : public TreeItem +{ +public: + NodeTreeItem(const QString &name, const QIcon &icon); + + NodeTreeItem *asNodeItem() override; + + QIcon icon() const override; + +private: + QIcon m_icon; +}; + + +class PropertyTreeItem : public TreeItem +{ +public: + PropertyTreeItem(const QString &name, const AnimationCurve &curve); + + PropertyTreeItem *asPropertyItem() override; + + AnimationCurve curve() const; - virtual double maximumValue() const = 0; + void setCurve(const AnimationCurve &curve); - virtual AnimationCurve curve(int i) const = 0; +private: + using TreeItem::addChild; - virtual CurveEditorStyle style() const { return CurveEditorStyle(); } + AnimationCurve m_curve; }; } // End namespace DesignTools. diff --git a/src/curveeditor/curveeditorstyle.h b/src/curveeditor/curveeditorstyle.h index 23fdaf8..f60948a 100644 --- a/src/curveeditor/curveeditorstyle.h +++ b/src/curveeditor/curveeditorstyle.h @@ -25,12 +25,23 @@ #pragma once +#include <QBitmap> #include <QBrush> #include <QColor> #include <QDialog> +#include <QIcon> namespace DesignTools { +struct TreeItemStyleOption +{ + double margins; + QIcon pinnedIcon = QIcon(":/ICON_PINNED"); + QIcon unpinnedIcon = QIcon(":/ICON_UNPINNED"); + QIcon lockedIcon = QIcon(":/ICON_LOCKED"); + QIcon unlockedIcon = QIcon(":/ICON_UNLOCKED"); +}; + struct HandleItemStyleOption { double size = 10.0; @@ -92,6 +103,17 @@ struct CurveEditorStyle KeyframeItemStyleOption keyframeStyle; CurveItemStyleOption curveStyle; + + TreeItemStyleOption treeItemStyle; }; +inline QPixmap pixmapFromIcon(const QIcon &icon, const QSize &size, const QColor &color) +{ + QPixmap pixmap = icon.pixmap(size); + QPixmap mask(pixmap.size()); + mask.fill(color); + mask.setMask(pixmap.createMaskFromColor(Qt::transparent)); + return mask; +} + } // End namespace DesignTools. diff --git a/src/curveeditor/colorcontrol.cpp b/src/curveeditor/detail/colorcontrol.cpp index a833a4c..a833a4c 100644 --- a/src/curveeditor/colorcontrol.cpp +++ b/src/curveeditor/detail/colorcontrol.cpp diff --git a/src/curveeditor/colorcontrol.h b/src/curveeditor/detail/colorcontrol.h index 54dfe19..54dfe19 100644 --- a/src/curveeditor/colorcontrol.h +++ b/src/curveeditor/detail/colorcontrol.h diff --git a/src/curveeditor/curveeditorstyledialog.cpp b/src/curveeditor/detail/curveeditorstyledialog.cpp index 2629f24..fc34737 100644 --- a/src/curveeditor/curveeditorstyledialog.cpp +++ b/src/curveeditor/detail/curveeditorstyledialog.cpp @@ -199,7 +199,8 @@ void CurveEditorStyleDialog::emitStyleChanged() void CurveEditorStyleDialog::printStyle() { auto toString = [](const QColor &color) { - QString tmp = QString("QColor(%1, %2, %3)").arg(color.red()).arg(color.green()).arg(color.blue()); + QString tmp + = QString("QColor(%1, %2, %3)").arg(color.red()).arg(color.green()).arg(color.blue()); return qPrintable(tmp); }; diff --git a/src/curveeditor/curveeditorstyledialog.h b/src/curveeditor/detail/curveeditorstyledialog.h index 645f545..645f545 100644 --- a/src/curveeditor/curveeditorstyledialog.h +++ b/src/curveeditor/detail/curveeditorstyledialog.h diff --git a/src/curveeditor/curveitem.cpp b/src/curveeditor/detail/curveitem.cpp index 128b60e..2d8e462 100644 --- a/src/curveeditor/curveitem.cpp +++ b/src/curveeditor/detail/curveitem.cpp @@ -23,6 +23,7 @@ ** ****************************************************************************/ #include "curveitem.h" +#include "animationcurve.h" #include "keyframeitem.h" #include "utils.h" @@ -33,18 +34,33 @@ namespace DesignTools { CurveItem::CurveItem(QGraphicsItem *parent) : QGraphicsObject(parent) + , m_id(0) + , m_style() , m_transform() , m_keyframes() + , m_selected(false) + , m_preselected(false) + , m_itemDirty(true) + , m_pathDirty(true) {} -CurveItem::CurveItem(const AnimationCurve &curve, QGraphicsItem *parent) +CurveItem::CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem *parent) : QGraphicsObject(parent) + , m_id(id) + , m_style() , m_transform() , m_keyframes() + , m_selected(false) + , m_preselected(false) + , m_itemDirty(true) + , m_pathDirty(true) { + setAcceptHoverEvents(true); + auto emitCurveChanged = [this]() { + m_itemDirty = true; + m_pathDirty = true; update(); - emit curveChanged(this->curve()); }; for (auto frame : curve.keyframes()) { @@ -63,65 +79,81 @@ int CurveItem::type() const QRectF CurveItem::boundingRect() const { - QRectF bounds; - - auto bbox = [&bounds](const Keyframe &frame) { + auto bbox = [](QRectF &bounds, const Keyframe &frame) { grow(bounds, frame.position()); grow(bounds, frame.leftHandle()); grow(bounds, frame.rightHandle()); }; + QRectF bounds; for (auto *item : m_keyframes) - bbox(item->keyframe()); + bbox(bounds, item->keyframe()); return m_transform.mapRect(bounds); } -void CurveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +bool CurveItem::contains(const QPointF &point) const { - if (m_keyframes.size() > 1) { - painter->save(); + bool valid = false; + QPointF transformed(m_transform.inverted(&valid).map(point)); - QPen pen = painter->pen(); - pen.setWidthF(m_style.width); - pen.setColor(isSelected() ? m_style.selectionColor : m_style.color); - painter->setPen(pen); + double width = abs(20.0 / scaleY(m_transform)); - Keyframe previous = m_keyframes.front()->keyframe(); - Keyframe current; - for (size_t i = 1; i < m_keyframes.size(); ++i) { - current = m_keyframes[i]->keyframe(); + if (valid) + return curve().intersects(transformed, width); - QPainterPath path(m_transform.map(previous.position())); + return false; +} - if (previous.rightHandle().isNull() || current.leftHandle().isNull()) - path.lineTo(m_transform.map(current.position())); - else - path.cubicTo( - m_transform.map(previous.rightHandle()), - m_transform.map(current.leftHandle()), - m_transform.map(current.position())); +void CurveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + if (m_keyframes.size() > 1) { + QPen pen = painter->pen(); + pen.setWidthF(m_style.width); + pen.setColor(m_selected ? m_style.selectionColor : (m_preselected ? Qt::red : m_style.color)); - painter->drawPath(path); + painter->save(); + painter->setPen(pen); + painter->drawPath(path()); - previous = current; - } + // Tmp. Remove later. + painter->setPen(Qt::red); + for (auto p : curve().extrema()) + painter->drawEllipse(m_transform.map(p), 6., 6.); painter->restore(); } } +bool CurveItem::isDirty() const +{ + return m_itemDirty; +} + +unsigned int CurveItem::id() const +{ + return m_id; +} + AnimationCurve CurveItem::curve() const { std::vector<Keyframe> out; - for (auto item : m_keyframes) { + out.reserve(m_keyframes.size()); + for (auto item : m_keyframes) out.push_back(item->keyframe()); - } + return out; } +void CurveItem::setDirty(bool dirty) +{ + m_itemDirty = dirty; +} + QRectF CurveItem::setComponentTransform(const QTransform &transform) { + m_pathDirty = true; + prepareGeometryChange(); m_transform = transform; for (auto frame : m_keyframes) @@ -138,4 +170,47 @@ void CurveItem::setStyle(const CurveEditorStyle &style) frame->setStyle(style); } +QPainterPath CurveItem::path() const +{ + if (m_pathDirty) { + Keyframe previous = m_keyframes.front()->keyframe(); + Keyframe current; + + m_path = QPainterPath(m_transform.map(previous.position())); + for (size_t i = 1; i < m_keyframes.size(); ++i) { + current = m_keyframes[i]->keyframe(); + + if (previous.rightHandle().isNull() || current.leftHandle().isNull()) { + m_path.lineTo(m_transform.map(current.position())); + } else { + m_path.cubicTo( + m_transform.map(previous.rightHandle()), + m_transform.map(current.leftHandle()), + m_transform.map(current.position())); + } + + previous = current; + } + m_pathDirty = false; + } + + return m_path; +} + +void CurveItem::setSelected(bool select) +{ + if (select != m_selected) { + m_selected = select; + update(); + } +} + +void CurveItem::setPreSelected(bool preSelect) +{ + if (preSelect != m_preselected) { + m_preselected = preSelect; + update(); + } +} + } // End namespace DesignTools. diff --git a/src/curveeditor/curveitem.h b/src/curveeditor/detail/curveitem.h index bae12e8..cf9b29f 100644 --- a/src/curveeditor/curveitem.h +++ b/src/curveeditor/detail/curveitem.h @@ -25,26 +25,23 @@ #pragma once -#include "animationcurve.h" #include "curveeditorstyle.h" #include <QGraphicsObject> namespace DesignTools { +class AnimationCurve; class KeyframeItem; class CurveItem : public QGraphicsObject { Q_OBJECT -signals: - void curveChanged(const AnimationCurve &curve); - public: CurveItem(QGraphicsItem *parent = nullptr); - CurveItem(const AnimationCurve &curve, QGraphicsItem *parent = nullptr); + CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem *parent = nullptr); ~CurveItem() override; @@ -54,22 +51,47 @@ public: QRectF boundingRect() const override; + bool contains(const QPointF &point) const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + bool isDirty() const; + + unsigned int id() const; + AnimationCurve curve() const; + void setDirty(bool dirty); + QRectF setComponentTransform(const QTransform &transform); void setStyle(const CurveEditorStyle &style); + void setSelected(bool select); + + void setPreSelected(bool preSelect); + private: - QTransform m_transform; + QPainterPath path() const; + +private: + unsigned int m_id; CurveItemStyleOption m_style; - AnimationCurve m_curve; + QTransform m_transform; std::vector<KeyframeItem *> m_keyframes; + + bool m_selected; + + bool m_preselected; + + bool m_itemDirty; + + mutable bool m_pathDirty; + + mutable QPainterPath m_path; }; } // End namespace DesignTools. diff --git a/src/curveeditor/detail/curvesegment.cpp b/src/curveeditor/detail/curvesegment.cpp new file mode 100644 index 0000000..1bd0ad9 --- /dev/null +++ b/src/curveeditor/detail/curvesegment.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** 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 "curvesegment.h" + +#include <qmath.h> + +#include <assert.h> + +namespace DesignTools { + +class CubicPolynomial +{ +public: + CubicPolynomial(double p0, double p1, double p2, double p3); + + double evaluate(double x) const; + + std::vector<double> extrema() const; + + std::vector<double> roots() const; + +private: + double m_a; + double m_b; + double m_c; + double m_d; +}; + +CubicPolynomial::CubicPolynomial(double p0, double p1, double p2, double p3) + : m_a(p3 - 3.0 * p2 + 3.0 * p1 - p0) + , m_b(3.0 * p2 - 6.0 * p1 + 3.0 * p0) + , m_c(3.0 * p1 - 3.0 * p0) + , m_d(p0) +{} + +std::vector<double> CubicPolynomial::extrema() const +{ + std::vector<double> out; + + auto addValidValue = [&out](double value) { + if (!(std::isnan(value) || std::isinf(value))) + out.push_back(value); + }; + + // Find the roots of the first derivative of y. + auto pd2 = (2.0 * m_b) / (3.0 * m_a) / 2.0; + auto q = m_c / (3.0 * m_a); + + auto radi = std::pow(pd2, 2.0) - q; + + auto x1 = -pd2 + std::sqrt(radi); + auto x2 = -pd2 - std::sqrt(radi); + + addValidValue(x1); + addValidValue(x2); + + return out; +} + +std::vector<double> CubicPolynomial::roots() const +{ + std::vector<double> out; + + auto addValidValue = [&out](double value) { + if (!(std::isnan(value) || std::isinf(value))) + out.push_back(value); + }; + + if (m_a == 0.0) { + // Linear + if (m_b == 0.0) { + if (m_c != 0.0) + out.push_back(-m_d / m_c); + // Quadratic + } else { + const double p = m_c / m_b / 2.0; + const double q = m_d / m_b; + addValidValue(-p + std::sqrt(std::pow(p, 2.0) - q)); + addValidValue(-p - std::sqrt(std::pow(p, 2.0) - q)); + } + // Cubic + } else { + const double p = 3.0 * m_a * m_c - std::pow(m_b, 2.0); + const double q = 2.0 * std::pow(m_b, 3.0) - 9.0 * m_a * m_b * m_c + + 27.0 * std::pow(m_a, 2.0) * m_d; + + auto disc = std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0); + + auto toX = [&](double y) { return (y - m_b) / (3.0 * m_a); }; + + // One real solution. + if (disc >= 0) { + auto u = (1.0 / 2.0) + * std::cbrt(-4.0 * q + + 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0))); + auto v = (1.0 / 2.0) + * std::cbrt(-4.0 * q + - 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0))); + + addValidValue(toX(u + v)); + // Three real solutions. + } else { + auto phi = acos(-q / (2 * std::sqrt(-std::pow(p, 3.0)))); + auto y1 = std::sqrt(-p) * 2.0 * cos(phi / 3.0); + auto y2 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (2.0 * M_PI / 3.0)); + auto y3 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (4.0 * M_PI / 3.0)); + + addValidValue(toX(y1)); + addValidValue(toX(y2)); + addValidValue(toX(y3)); + } + } + return out; +} + +CurveSegment::CurveSegment() + : m_left() + , m_right() +{} + +CurveSegment::CurveSegment(const Keyframe &left, const Keyframe &right) + : m_left(left) + , m_right(right) +{} + +bool CurveSegment::containsX(double x) const +{ + return m_left.position().x() <= x && m_right.position().x() >= x; +} + +double evaluateForT(double t, double p0, double p1, double p2, double p3) +{ + assert(t >= 0. && t <= 1.); + + const double it = 1.0 - t; + + return p0 * std::pow(it, 3.0) + p1 * 3.0 * std::pow(it, 2.0) * t + + p2 * 3.0 * it * std::pow(t, 2.0) + p3 * std::pow(t, 3.0); +} + +QPointF CurveSegment::evaluate(double t) const +{ + const double x = evaluateForT( + t, + m_left.position().x(), + m_left.rightHandle().x(), + m_right.leftHandle().x(), + m_right.position().x()); + + const double y = evaluateForT( + t, + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + return QPointF(x, y); +} + +std::vector<QPointF> CurveSegment::extrema() const +{ + std::vector<QPointF> out; + + auto polynomial = CubicPolynomial( + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + for (double t : polynomial.extrema()) { + if (t < 0.0 || t > 1.0) + continue; + + const double x = evaluateForT( + t, + m_left.position().x(), + m_left.rightHandle().x(), + m_right.leftHandle().x(), + m_right.position().x()); + + const double y = evaluateForT( + t, + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + out.push_back(QPointF(x, y)); + } + return out; +} + +std::vector<double> CurveSegment::yForX(double x) const +{ + std::vector<double> out; + + auto polynomial = CubicPolynomial( + m_left.position().x() - x, + m_left.rightHandle().x() - x, + m_right.leftHandle().x() - x, + m_right.position().x() - x); + + for (double t : polynomial.roots()) { + if (t < 0.0 || t > 1.0) + continue; + + const double y = evaluateForT( + t, + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + out.push_back(y); + } + + return out; +} + +std::vector<double> CurveSegment::xForY(double y) const +{ + std::vector<double> out; + + auto polynomial = CubicPolynomial( + m_left.position().y() - y, + m_left.rightHandle().y() - y, + m_right.leftHandle().y() - y, + m_right.position().y() - y); + + for (double t : polynomial.roots()) { + if (t < 0.0 || t > 1.0) + continue; + + const double x = evaluateForT( + t, + m_left.position().x(), + m_left.rightHandle().x(), + m_right.leftHandle().x(), + m_right.position().x()); + + out.push_back(x); + } + + return out; +} + +void CurveSegment::setLeft(const Keyframe &frame) +{ + m_left = frame; +} + +void CurveSegment::setRight(const Keyframe &frame) +{ + m_right = frame; +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/curvesegment.h b/src/curveeditor/detail/curvesegment.h new file mode 100644 index 0000000..ce2d700 --- /dev/null +++ b/src/curveeditor/detail/curvesegment.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "keyframe.h" + +#include <vector> + +class QPointF; + +namespace DesignTools { + +class CurveSegment +{ +public: + CurveSegment(); + + CurveSegment(const Keyframe &first, const Keyframe &last); + + bool containsX(double x) const; + + QPointF evaluate(double t) const; + + std::vector<QPointF> extrema() const; + + std::vector<double> yForX(double x) const; + + std::vector<double> xForY(double y) const; + + void setLeft(const Keyframe &frame); + + void setRight(const Keyframe &frame); + +private: + Keyframe m_left; + + Keyframe m_right; +}; + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/graphicsscene.cpp b/src/curveeditor/detail/graphicsscene.cpp new file mode 100644 index 0000000..ddafaf4 --- /dev/null +++ b/src/curveeditor/detail/graphicsscene.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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 "graphicsscene.h" +#include "animationcurve.h" +#include "curveitem.h" + +#include <QGraphicsSceneMouseEvent> + +namespace DesignTools { + +GraphicsScene::GraphicsScene(QObject *parent) + : QGraphicsScene(parent) + , m_dirty(true) + , m_limits() +{} + +double GraphicsScene::minimumTime() const +{ + return limits().left(); +} + +double GraphicsScene::maximumTime() const +{ + return limits().right(); +} + +double GraphicsScene::minimumValue() const +{ + return limits().bottom(); +} + +double GraphicsScene::maximumValue() const +{ + return limits().top(); +} + +void GraphicsScene::addCurveItem(CurveItem *item) +{ + m_dirty = true; + addItem(item); +} + +void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + QGraphicsScene::mouseMoveEvent(mouseEvent); + + if (!mouseEvent->isAccepted()) { + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) { + curveItem->setPreSelected(curveItem->contains(mouseEvent->scenePos())); + } + } + } +} + +void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + QGraphicsScene::mouseReleaseEvent(mouseEvent); + + clearSelection(); + + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) { + if (curveItem->contains(mouseEvent->scenePos())) + curveItem->setSelected(true); + + if (curveItem->isDirty()) { + m_dirty = true; + emit curveChanged(curveItem->id(), curveItem->curve()); + curveItem->setDirty(false); + } + } + } +} + +QRectF GraphicsScene::limits() const +{ + if (m_dirty) { + QPointF min(std::numeric_limits<double>::max(), std::numeric_limits<double>::max()); + QPointF max(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest()); + + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) { + auto curve = curveItem->curve(); + if (min.x() > curve.minimumTime()) + min.rx() = curve.minimumTime(); + + if (min.y() > curve.minimumValue()) + min.ry() = curve.minimumValue(); + + if (max.x() < curve.maximumTime()) + max.rx() = curve.maximumTime(); + + if (max.y() < curve.maximumValue()) + max.ry() = curve.maximumValue(); + } + } + + m_limits = QRectF(QPointF(min.x(), max.y()), QPointF(max.x(), min.y())); + m_dirty = false; + } + return m_limits; +} + +void GraphicsScene::clearSelection() +{ + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(item)) { + curveItem->setSelected(false); + } + } +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/graphicsscene.h b/src/curveeditor/detail/graphicsscene.h new file mode 100644 index 0000000..7f80436 --- /dev/null +++ b/src/curveeditor/detail/graphicsscene.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QGraphicsScene> + +namespace DesignTools { + +class AnimationCurve; +class CurveItem; + +class GraphicsScene : public QGraphicsScene +{ + Q_OBJECT + +signals: + void curveChanged(unsigned int id, const AnimationCurve &curve); + +public: + GraphicsScene(QObject *parent = nullptr); + + double minimumTime() const; + + double maximumTime() const; + + double minimumValue() const; + + double maximumValue() const; + + void addCurveItem(CurveItem *item); + +protected: + void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override; + + void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override; + +private: + using QGraphicsScene::addItem; + + QRectF limits() const; + + void clearSelection(); + + mutable bool m_dirty; + + mutable QRectF m_limits; +}; + +} // End namespace DesignTools. diff --git a/src/curveeditor/curveeditorview.cpp b/src/curveeditor/detail/graphicsview.cpp index 0a54c82..e6dfb11 100644 --- a/src/curveeditor/curveeditorview.cpp +++ b/src/curveeditor/detail/graphicsview.cpp @@ -22,7 +22,7 @@ ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ -#include "curveeditorview.h" +#include "graphicsview.h" #include "curveeditormodel.h" #include "curveitem.h" #include "utils.h" @@ -35,7 +35,7 @@ namespace DesignTools { -CurveEditorView::CurveEditorView(CurveEditorModel *model, QWidget *parent) +GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent) : QGraphicsView(parent) , m_scene() , m_style(model->style()) @@ -50,15 +50,20 @@ CurveEditorView::CurveEditorView(CurveEditorModel *model, QWidget *parent) setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); - connect(&m_dialog, &CurveEditorStyleDialog::styleChanged, this, &CurveEditorView::setStyle); + connect(&m_dialog, &CurveEditorStyleDialog::styleChanged, this, &GraphicsView::setStyle); - m_scene.addItem(new CurveItem(model->curve(0))); + 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 CurveEditorView::setStyle(const CurveEditorStyle &style) +void GraphicsView::setStyle(const CurveEditorStyle &style) { m_style = style; @@ -68,31 +73,40 @@ void CurveEditorView::setStyle(const CurveEditorStyle &style) curveItem->setStyle(style); } - updateSceneRect(); applyZoom(m_zoomX, m_zoomY); viewport()->update(); } -void CurveEditorView::zoomX(double zoom) +void GraphicsView::zoomX(double zoom) { applyZoom(zoom, m_zoomY); - update(); + viewport()->update(); } -void CurveEditorView::zoomY(double zoom) +void GraphicsView::zoomY(double zoom) { applyZoom(m_zoomX, zoom); - update(); + 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 CurveEditorView::resizeEvent(QResizeEvent *event) +void GraphicsView::resizeEvent(QResizeEvent *event) { - updateSceneRect(); applyZoom(m_zoomX, m_zoomY); QGraphicsView::resizeEvent(event); } -void CurveEditorView::contextMenuEvent(QContextMenuEvent *event) +void GraphicsView::contextMenuEvent(QContextMenuEvent *event) { Q_UNUSED(event); @@ -105,20 +119,22 @@ void CurveEditorView::contextMenuEvent(QContextMenuEvent *event) menu.exec(event->globalPos()); } -void CurveEditorView::drawForeground(QPainter *painter, const QRectF &rect) +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())); - drawTimeScale(painter, abscissa); + if (abscissa.isValid()) + drawTimeScale(painter, abscissa); auto ordinate = QRectF(gap.bottomLeft(), rect.bottomLeft() + QPointF(gap.width(), 0.0)); - drawValueScale(painter, ordinate); + if (ordinate.isValid()) + drawValueScale(painter, ordinate); painter->fillRect(gap, m_style.backgroundAlternateBrush); } -void CurveEditorView::drawBackground(QPainter *painter, const QRectF &rect) +void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect) { painter->fillRect(rect, m_style.backgroundBrush); painter->fillRect(scene()->sceneRect(), m_style.backgroundAlternateBrush); @@ -128,27 +144,27 @@ void CurveEditorView::drawBackground(QPainter *painter, const QRectF &rect) drawExtremaY(painter, rect); } -int CurveEditorView::mapTimeToX(double time) +int GraphicsView::mapTimeToX(double time) { return std::round(time * scaleX(m_transform)); } -int CurveEditorView::mapValueToY(double y) +int GraphicsView::mapValueToY(double y) { return std::round(y * scaleY(m_transform)); } -double CurveEditorView::mapXtoTime(int x) +double GraphicsView::mapXtoTime(int x) { return static_cast<double>(x) / scaleX(m_transform); } -double CurveEditorView::mapYtoValue(int y) +double GraphicsView::mapYtoValue(int y) { return static_cast<double>(y) / scaleY(m_transform); } -void CurveEditorView::applyZoom(double x, double y) +void GraphicsView::applyZoom(double x, double y) { m_zoomX = x; m_zoomY = y; @@ -159,7 +175,7 @@ void CurveEditorView::applyZoom(double x, double y) 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_model->maximumValue() - m_model->minimumValue()); + 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); @@ -173,7 +189,7 @@ void CurveEditorView::applyZoom(double x, double y) updateSceneRect(sceneBounds); } -void CurveEditorView::updateSceneRect(const QRectF &rect) +void GraphicsView::updateSceneRect(const QRectF &rect) { if (rect.isValid()) scene()->setSceneRect(rect); @@ -187,21 +203,24 @@ void CurveEditorView::updateSceneRect(const QRectF &rect) setSceneRect(sr); } -void CurveEditorView::drawGrid(QPainter *painter, const QRectF &rect) +void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect) { - painter->save(); - painter->setPen(m_style.gridColor); - 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)); @@ -209,7 +228,7 @@ void CurveEditorView::drawGrid(QPainter *painter, const QRectF &rect) painter->restore(); } -void CurveEditorView::drawExtremaX(QPainter *painter, const QRectF &rect) +void GraphicsView::drawExtremaX(QPainter *painter, const QRectF &rect) { auto drawVerticalLine = [rect, painter](double position) { painter->drawLine(position, rect.top(), position, rect.bottom()); @@ -222,7 +241,7 @@ void CurveEditorView::drawExtremaX(QPainter *painter, const QRectF &rect) painter->restore(); } -void CurveEditorView::drawExtremaY(QPainter *painter, const QRectF &rect) +void GraphicsView::drawExtremaY(QPainter *painter, const QRectF &rect) { auto drawHorizontalLine = [rect, painter](double position) { painter->drawLine(rect.left(), position, rect.right(), position); @@ -230,15 +249,15 @@ void CurveEditorView::drawExtremaY(QPainter *painter, const QRectF &rect) painter->save(); painter->setPen(Qt::blue); - drawHorizontalLine(mapValueToY(m_model->minimumValue())); - drawHorizontalLine(mapValueToY(m_model->maximumValue())); + drawHorizontalLine(mapValueToY(m_scene.minimumValue())); + drawHorizontalLine(mapValueToY(m_scene.maximumValue())); drawHorizontalLine(mapValueToY(0.0)); painter->restore(); } -void CurveEditorView::drawTimeScale(QPainter *painter, const QRectF &rect) +void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect) { painter->save(); painter->setPen(m_style.fontColor); @@ -264,7 +283,7 @@ void CurveEditorView::drawTimeScale(QPainter *painter, const QRectF &rect) painter->restore(); } -void CurveEditorView::drawValueScale(QPainter *painter, const QRectF &rect) +void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect) { painter->save(); painter->setPen(m_style.fontColor); @@ -281,12 +300,12 @@ void CurveEditorView::drawValueScale(QPainter *painter, const QRectF &rect) painter->drawText(textRect, Qt::AlignCenter, valueText); }; - paintLabeledTick(m_model->minimumValue()); - paintLabeledTick(m_model->maximumValue()); + paintLabeledTick(m_scene.minimumValue()); + paintLabeledTick(m_scene.maximumValue()); painter->restore(); } -double CurveEditorView::timeLabelInterval(QPainter *painter, double maxTime) +double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime) { QFontMetrics fm(painter->font()); int minTextSpacing = fm.width(QString("X%1X").arg(maxTime)); diff --git a/src/curveeditor/curveeditorview.h b/src/curveeditor/detail/graphicsview.h index fe35933..db9933e 100644 --- a/src/curveeditor/curveeditorview.h +++ b/src/curveeditor/detail/graphicsview.h @@ -27,20 +27,21 @@ #include "curveeditorstyle.h" #include "curveeditorstyledialog.h" +#include "graphicsscene.h" -#include <QGraphicsScene> #include <QGraphicsView> namespace DesignTools { +class CurveItem; class CurveEditorModel; -class CurveEditorView : public QGraphicsView +class GraphicsView : public QGraphicsView { Q_OBJECT public: - CurveEditorView(CurveEditorModel *model, QWidget *parent = nullptr); + GraphicsView(CurveEditorModel *model, QWidget *parent = nullptr); void setStyle(const CurveEditorStyle &style); @@ -48,6 +49,8 @@ public: void zoomY(double zoom); + void reset(const std::vector<CurveItem *> &items); + protected: void resizeEvent(QResizeEvent *event) override; @@ -83,7 +86,7 @@ private: double timeLabelInterval(QPainter *painter, double maxTime); private: - QGraphicsScene m_scene; + GraphicsScene m_scene; CurveEditorStyle m_style; diff --git a/src/curveeditor/handleitem.cpp b/src/curveeditor/detail/handleitem.cpp index 4653af6..d9abc77 100644 --- a/src/curveeditor/handleitem.cpp +++ b/src/curveeditor/detail/handleitem.cpp @@ -59,8 +59,7 @@ HandleItem::HandleItem(QGraphicsItem *parent) setFlag(QGraphicsItem::ItemStacksBehindParent, true); } -HandleItem::~HandleItem() -{ } +HandleItem::~HandleItem() {} int HandleItem::type() const { @@ -120,7 +119,6 @@ bool HandleItem::sceneEvent(QEvent *event) }; switch (event->type()) { - case QEvent::GraphicsSceneMousePress: if (!intersectsHandle()) event->ignore(); diff --git a/src/curveeditor/handleitem.h b/src/curveeditor/detail/handleitem.h index 8e2ffd7..8e2ffd7 100644 --- a/src/curveeditor/handleitem.h +++ b/src/curveeditor/detail/handleitem.h diff --git a/src/curveeditor/keyframeitem.cpp b/src/curveeditor/detail/keyframeitem.cpp index 867585c..867585c 100644 --- a/src/curveeditor/keyframeitem.cpp +++ b/src/curveeditor/detail/keyframeitem.cpp diff --git a/src/curveeditor/keyframeitem.h b/src/curveeditor/detail/keyframeitem.h index 39221d2..88eb366 100644 --- a/src/curveeditor/keyframeitem.h +++ b/src/curveeditor/detail/keyframeitem.h @@ -25,8 +25,8 @@ #pragma once -#include "animationcurve.h" #include "curveeditorstyle.h" +#include "keyframe.h" #include <QGraphicsObject> diff --git a/src/curveeditor/detail/treeitem.cpp b/src/curveeditor/detail/treeitem.cpp new file mode 100644 index 0000000..c8f5360 --- /dev/null +++ b/src/curveeditor/detail/treeitem.cpp @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** 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 "treeitem.h" + +#include <QIcon> +#include <QVariant> + +namespace DesignTools { + +TreeItem::TreeItem(const QString &name) + : m_name(name) + , m_id(0) + , m_locked(false) + , m_pinned(false) + , m_parent(nullptr) + , m_children() +{} + +TreeItem::~TreeItem() +{ + m_parent = nullptr; + + qDeleteAll(m_children); +} + +QIcon TreeItem::icon() const +{ + return QIcon(); +} + +NodeTreeItem *TreeItem::asNodeItem() +{ + return nullptr; +} + +PropertyTreeItem *TreeItem::asPropertyItem() +{ + return nullptr; +} + +unsigned int TreeItem::id() const +{ + return m_id; +} + +bool TreeItem::locked() const +{ + return m_locked; +} + +bool TreeItem::pinned() const +{ + return m_pinned; +} + +int TreeItem::row() const +{ + if (m_parent) { + for (size_t i = 0; i < m_parent->m_children.size(); ++i) { + if (m_parent->m_children[i] == this) + return i; + } + } + + return 0; +} + +int TreeItem::column() const +{ + return 0; +} + +int TreeItem::rowCount() const +{ + return m_children.size(); +} + +int TreeItem::columnCount() const +{ + return 3; +} + +TreeItem *TreeItem::parent() const +{ + return m_parent; +} + +TreeItem *TreeItem::child(int row) const +{ + if (row < 0 || row >= static_cast<int>(m_children.size())) + return nullptr; + + return m_children.at(row); +} + +TreeItem *TreeItem::find(unsigned int id) const +{ + for (auto *child : m_children) { + if (child->id() == id) + return child; + + if (auto *childsChild = child->find(id)) + return childsChild; + } + + return nullptr; +} + +QVariant TreeItem::data(int column) const +{ + switch (column) { + case 0: + return QVariant(m_name); + case 1: + return QVariant(m_locked); + case 2: + return QVariant(m_pinned); + case 3: + return QVariant(m_id); + default: + return QVariant(); + } +} + +QVariant TreeItem::headerData(int column) const +{ + switch (column) { + case 0: + return QString("Name"); + case 1: + return QString("L"); + case 2: + return QString("P"); + case 3: + return QString("Id"); + default: + return QVariant(); + } +} + +void TreeItem::setId(unsigned int &id) +{ + m_id = id; + + for (auto *child : m_children) + child->setId(++id); +} + +void TreeItem::addChild(TreeItem *child) +{ + child->m_parent = this; + m_children.push_back(child); +} + +void TreeItem::setLocked(bool locked) +{ + m_locked = locked; +} + +void TreeItem::setPinned(bool pinned) +{ + m_pinned = pinned; +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treeitem.h b/src/curveeditor/detail/treeitem.h new file mode 100644 index 0000000..15a1014 --- /dev/null +++ b/src/curveeditor/detail/treeitem.h @@ -0,0 +1,102 @@ + +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "animationcurve.h" + +#include <QString> + +#include <vector> + +class QIcon; +class QVariant; + +namespace DesignTools { + +class NodeTreeItem; +class PropertyTreeItem; + +class TreeItem +{ +public: + TreeItem(const QString &name); + + virtual ~TreeItem(); + + virtual QIcon icon() const; + + virtual NodeTreeItem *asNodeItem(); + + virtual PropertyTreeItem *asPropertyItem(); + + unsigned int id() const; + + bool locked() const; + + bool pinned() const; + + int row() const; + + int column() const; + + int rowCount() const; + + int columnCount() const; + + TreeItem *parent() const; + + TreeItem *child(int row) const; + + TreeItem *find(unsigned int row) const; + + QVariant data(int column) const; + + QVariant headerData(int column) const; + + void setId(unsigned int &id); + + void addChild(TreeItem *child); + + void setLocked(bool locked); + + void setPinned(bool pinned); + +protected: + QString m_name; + + unsigned int m_id; + + bool m_locked; + + bool m_pinned; + + TreeItem *m_parent; + + std::vector<TreeItem *> m_children; +}; + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treeitemdelegate.cpp b/src/curveeditor/detail/treeitemdelegate.cpp new file mode 100644 index 0000000..21633dd --- /dev/null +++ b/src/curveeditor/detail/treeitemdelegate.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** 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 "treeitemdelegate.h" +#include "treeitem.h" + +#include <QEvent> +#include <QMouseEvent> +#include <QPainter> + +namespace DesignTools { + +TreeItemDelegate::TreeItemDelegate(const CurveEditorStyle &style, QObject *parent) + : QStyledItemDelegate(parent) + , m_style(style) +{} + +QSize TreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + return QStyledItemDelegate::sizeHint(option, index); +} + +void TreeItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (index.column() == 1 || index.column() == 2) { + + int height = option.rect.size().height(); + QRect iconRect(QPoint(0, 0), QSize(height, height)); + iconRect.moveCenter(option.rect.center()); + + auto *treeItem = static_cast<TreeItem *>(index.internalPointer()); + if (option.state & QStyle::State_MouseOver && iconRect.contains(m_mousePos)) { + + painter->fillRect(option.rect, option.backgroundBrush); + + if (index.column() == 1) { + + if (treeItem->locked()) { + + QPixmap pixmap = pixmapFromIcon( + m_style.treeItemStyle.unlockedIcon, + iconRect.size(), + m_style.fontColor); + + painter->drawPixmap(iconRect, pixmap); + + } else { + + QPixmap pixmap = pixmapFromIcon( + m_style.treeItemStyle.lockedIcon, + iconRect.size(), + m_style.fontColor); + + painter->drawPixmap(iconRect, pixmap); + } + + } else if (index.column() == 2) { + + if (treeItem->pinned()) { + + QPixmap pixmap = pixmapFromIcon( + m_style.treeItemStyle.unpinnedIcon, + iconRect.size(), + m_style.fontColor); + + painter->drawPixmap(iconRect, pixmap); + + } else { + + QPixmap pixmap = pixmapFromIcon( + m_style.treeItemStyle.pinnedIcon, + iconRect.size(), + m_style.fontColor); + + painter->drawPixmap(iconRect, pixmap); + + } + } + + } else { + + if (treeItem->locked() && index.column() == 1) { + + QPixmap pixmap = pixmapFromIcon( + m_style.treeItemStyle.lockedIcon, + iconRect.size(), + m_style.fontColor); + + painter->drawPixmap(iconRect, pixmap); + + } else if (treeItem->pinned() && index.column() == 2) { + + QPixmap pixmap = pixmapFromIcon( + m_style.treeItemStyle.pinnedIcon, + iconRect.size(), + m_style.fontColor); + + painter->drawPixmap(iconRect, pixmap); + + } + } + } else { + QStyledItemDelegate::paint(painter, option, index); + } +} + +void TreeItemDelegate::setStyle(const CurveEditorStyle &style) +{ + m_style = style; +} + +bool TreeItemDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + if (event->type() == QEvent::MouseMove) + m_mousePos = static_cast<QMouseEvent *>(event)->pos(); + + return QStyledItemDelegate::editorEvent(event, model, option, index); +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treeitemdelegate.h b/src/curveeditor/detail/treeitemdelegate.h new file mode 100644 index 0000000..6479f48 --- /dev/null +++ b/src/curveeditor/detail/treeitemdelegate.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "curveeditorstyle.h" + +#include <QStyledItemDelegate> + +namespace DesignTools { + +class TreeItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + TreeItemDelegate(const CurveEditorStyle &style, QObject *parent = nullptr); + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + void paint( + QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void setStyle(const CurveEditorStyle &style); + +protected: + bool editorEvent( + QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) override; + +private: + CurveEditorStyle m_style; + + QPoint m_mousePos; +}; + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treemodel.cpp b/src/curveeditor/detail/treemodel.cpp new file mode 100644 index 0000000..1fc3fad --- /dev/null +++ b/src/curveeditor/detail/treemodel.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** 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 "treemodel.h" +#include "treeitem.h" + +#include <QIcon> + +namespace DesignTools { + +TreeModel::TreeModel(QObject *parent) + : QAbstractItemModel(parent) + , m_root(new TreeItem("Root")) +{} + +TreeModel::~TreeModel() +{ + if (m_root) { + delete m_root; + m_root = nullptr; + } +} + +QVariant TreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + TreeItem *item = static_cast<TreeItem *>(index.internalPointer()); + + if (role == Qt::DecorationRole && index.column() == 0) + return item->icon(); + + if (role != Qt::DisplayRole) + return QVariant(); + + return item->data(index.column()); +} + +QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return m_root->headerData(section); + + return QVariant(); +} + +QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + TreeItem *parentItem = m_root; + + if (parent.isValid()) + parentItem = static_cast<TreeItem *>(parent.internalPointer()); + + if (TreeItem *childItem = parentItem->child(row)) + return createIndex(row, column, childItem); + + return QModelIndex(); +} + +QModelIndex TreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + TreeItem *childItem = static_cast<TreeItem *>(index.internalPointer()); + + if (TreeItem *parentItem = childItem->parent()) { + if (parentItem == m_root) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); + } + return QModelIndex(); +} + +int TreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + + TreeItem *parentItem = m_root; + + if (parent.isValid()) + parentItem = static_cast<TreeItem *>(parent.internalPointer()); + + return parentItem->rowCount(); +} + +int TreeModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_root->columnCount(); +} + +void TreeModel::initialize() +{ + if (m_root) + delete m_root; + + m_root = new TreeItem("Root"); +} + +TreeItem *TreeModel::root() +{ + return m_root; +} + +TreeItem *TreeModel::find(unsigned int id) +{ + return m_root->find(id); +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treemodel.h b/src/curveeditor/detail/treemodel.h new file mode 100644 index 0000000..13d2518 --- /dev/null +++ b/src/curveeditor/detail/treemodel.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QAbstractItemModel> + +#include <vector> + +namespace DesignTools { + +class TreeItem; + +class TreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + TreeModel(QObject *parent = nullptr); + + ~TreeModel() override; + + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + +protected: + void initialize(); + + TreeItem *root(); + + TreeItem *find(unsigned int id); + +private: + TreeItem *m_root; +}; + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treeview.cpp b/src/curveeditor/detail/treeview.cpp new file mode 100644 index 0000000..3b9a4ef --- /dev/null +++ b/src/curveeditor/detail/treeview.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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 "treeview.h" +#include "curveeditormodel.h" +#include "curveitem.h" +#include "treeitem.h" +#include "treeitemdelegate.h" + +#include <QHeaderView> +#include <QMouseEvent> + +namespace DesignTools { + +TreeView::TreeView(CurveEditorModel *model, QWidget *parent) + : QTreeView(parent) +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setUniformRowHeights(true); + setRootIsDecorated(false); + setMouseTracking(true); + setHeaderHidden(true); + + model->setParent(this); + setModel(model); + + auto expandItems = [this]() { expandAll(); }; + connect(model, &QAbstractItemModel::modelReset, expandItems); + + auto *delegate = new TreeItemDelegate(model->style(), this); + setItemDelegate(delegate); + + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); + + connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &TreeView::changeSelection); + setStyle(model->style()); + + header()->setSectionResizeMode(0, QHeaderView::Stretch); + header()->setSectionResizeMode(1, QHeaderView::Fixed); + header()->setSectionResizeMode(2, QHeaderView::Fixed); + + header()->setStretchLastSection(false); + header()->resizeSection(1, 20); + header()->resizeSection(2, 20); +} + +void TreeView::setStyle(const CurveEditorStyle &style) +{ + QPalette pal = palette(); + pal.setBrush(QPalette::Base, style.backgroundBrush); + pal.setBrush(QPalette::Button, style.backgroundAlternateBrush); + pal.setBrush(QPalette::Text, style.fontColor); + + // Tmp to see what happens on windows/macOS. + pal.setBrush(backgroundRole(), Qt::white); + pal.setBrush(foregroundRole(), Qt::white); + + setPalette(pal); + + if (auto *delegate = qobject_cast<TreeItemDelegate *>(itemDelegate())) + delegate->setStyle(style); +} + +void TreeView::changeCurve(unsigned int id, const AnimationCurve &curve) +{ + if (auto *curveModel = qobject_cast<CurveEditorModel *>(model())) + curveModel->setCurve(id, curve); +} + +void TreeView::changeSelection(const QItemSelection &selected, const QItemSelection &deselected) +{ + Q_UNUSED(selected); + Q_UNUSED(deselected); + + std::vector<CurveItem *> curves; + for (auto index : selectedIndexes()) { + if (index.isValid() && index.column() == 0) { + auto *treeItem = static_cast<TreeItem *>(index.internalPointer()); + if (auto *propertyItem = treeItem->asPropertyItem()) + curves.push_back(new CurveItem(treeItem->id(), propertyItem->curve())); + } + } + + emit curvesSelected(curves); +} + +QSize TreeView::sizeHint() const +{ + return QSize(170, 300); +} + +void TreeView::mousePressEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + auto *treeItem = static_cast<TreeItem *>(index.internalPointer()); + if (index.column() == 1) + treeItem->setLocked(!treeItem->locked()); + else if (index.column() == 2) + treeItem->setPinned(!treeItem->pinned()); + } + QTreeView::mousePressEvent(event); +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/detail/treeview.h b/src/curveeditor/detail/treeview.h new file mode 100644 index 0000000..9d3af64 --- /dev/null +++ b/src/curveeditor/detail/treeview.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QTreeView> + +namespace DesignTools { + +class AnimationCurve; +class CurveEditorModel; +class CurveItem; + +struct CurveEditorStyle; + +class TreeView : public QTreeView +{ + Q_OBJECT + +signals: + void curvesSelected(const std::vector<CurveItem *> &curves); + +public: + TreeView(CurveEditorModel *model, QWidget *parent = nullptr); + + void changeCurve(unsigned int id, const AnimationCurve &curve); + + void setStyle(const CurveEditorStyle &style); + +protected: + QSize sizeHint() const override; + + void mousePressEvent(QMouseEvent *event) override; + +private: + void changeSelection(const QItemSelection &selected, const QItemSelection &deselected); +}; + +} // End namespace DesignTools. diff --git a/src/curveeditor/utils.cpp b/src/curveeditor/detail/utils.cpp index da74946..4933bcb 100644 --- a/src/curveeditor/utils.cpp +++ b/src/curveeditor/detail/utils.cpp @@ -24,6 +24,7 @@ ** ****************************************************************************/ +#include <QPalette> #include <QPointF> #include <QRectF> #include <QTransform> @@ -75,4 +76,32 @@ QRectF bbox(const QRectF &rect, const QTransform &transform) return out; } +QPalette singleColorPalette(const QColor &color) +{ + QPalette palette; + palette.setColor(QPalette::Window, color); + palette.setColor(QPalette::Background, color); + palette.setColor(QPalette::WindowText, color); + palette.setColor(QPalette::Foreground, color); + palette.setColor(QPalette::Base, color); + palette.setColor(QPalette::AlternateBase, color); + palette.setColor(QPalette::ToolTipBase, color); + palette.setColor(QPalette::ToolTipText, color); + palette.setColor(QPalette::Text, color); + + palette.setColor(QPalette::Button, color); + palette.setColor(QPalette::ButtonText, color); + palette.setColor(QPalette::BrightText, color); + palette.setColor(QPalette::Light, color); + palette.setColor(QPalette::Midlight, color); + palette.setColor(QPalette::Dark, color); + palette.setColor(QPalette::Mid, color); + palette.setColor(QPalette::Shadow, color); + palette.setColor(QPalette::Highlight, color); + palette.setColor(QPalette::HighlightedText, color); + palette.setColor(QPalette::Link, color); + palette.setColor(QPalette::LinkVisited, color); + return palette; +} + } // End namespace DesignTools. diff --git a/src/curveeditor/utils.h b/src/curveeditor/detail/utils.h index c8a44fb..450c805 100644 --- a/src/curveeditor/utils.h +++ b/src/curveeditor/detail/utils.h @@ -25,6 +25,8 @@ #pragma once +class QColor; +class QPalette; class QPointF; class QRectF; class QTransform; @@ -45,4 +47,6 @@ void grow(QRectF &rect, const QPointF &point); QRectF bbox(const QRectF &rect, const QTransform &transform); +QPalette singleColorPalette(const QColor &color); + } // End namespace DesignTools. diff --git a/src/curveeditor/keyframe.cpp b/src/curveeditor/keyframe.cpp new file mode 100644 index 0000000..8ff577c --- /dev/null +++ b/src/curveeditor/keyframe.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 "keyframe.h" + +namespace DesignTools { + +Keyframe::Keyframe() + : m_position() + , m_leftHandle() + , m_rightHandle() +{} + +Keyframe::Keyframe(const QPointF &position) + : m_position(position) + , m_leftHandle() + , m_rightHandle() +{} + +Keyframe::Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle) + : m_position(position) + , m_leftHandle(leftHandle) + , m_rightHandle(rightHandle) +{} + +bool Keyframe::hasLeftHandle() const +{ + return !m_leftHandle.isNull(); +} + +bool Keyframe::hasRightHandle() const +{ + return !m_rightHandle.isNull(); +} + +QPointF Keyframe::position() const +{ + return m_position; +} + +QPointF Keyframe::leftHandle() const +{ + return m_leftHandle; +} + +QPointF Keyframe::rightHandle() const +{ + return m_rightHandle; +} + +void Keyframe::setPosition(const QPointF &pos) +{ + m_position = pos; +} + +void Keyframe::setLeftHandle(const QPointF &pos) +{ + m_leftHandle = pos; +} + +void Keyframe::setRightHandle(const QPointF &pos) +{ + m_rightHandle = pos; +} + +} // End namespace DesignTools. diff --git a/src/curveeditor/keyframe.h b/src/curveeditor/keyframe.h new file mode 100644 index 0000000..5e60425 --- /dev/null +++ b/src/curveeditor/keyframe.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QPointF> + +namespace DesignTools { + +class Keyframe +{ +public: + Keyframe(); + + Keyframe(const QPointF &position); + + Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle); + + bool hasLeftHandle() const; + + bool hasRightHandle() const; + + QPointF position() const; + + QPointF leftHandle() const; + + QPointF rightHandle() const; + + void setPosition(const QPointF &pos); + + void setLeftHandle(const QPointF &pos); + + void setRightHandle(const QPointF &pos); + +private: + QPointF m_position; + + QPointF m_leftHandle; + + QPointF m_rightHandle; +}; + +} // End namespace DesignTools. |