1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "splineeditor.h"
45 #include <QMouseEvent>
46 #include <QContextMenuEvent>
48 #include <QApplication>
49 #include <segmentproperties.h>
51 const int canvasWidth = 640;
52 const int canvasHeight = 320;
54 const int canvasMargin = 160;
56 SplineEditor::SplineEditor(QWidget *parent) :
57 QWidget(parent), m_pointListWidget(0), m_block(false)
59 setFixedSize(canvasWidth + canvasMargin * 2, canvasHeight + canvasMargin * 2);
61 m_controlPoints.append(QPointF(0.4, 0.075));
62 m_controlPoints.append(QPointF(0.45,0.24));
63 m_controlPoints.append(QPointF(0.5,0.5));
65 m_controlPoints.append(QPointF(0.55,0.76));
66 m_controlPoints.append(QPointF(0.7,0.9));
67 m_controlPoints.append(QPointF(1.0, 1.0));
69 m_numberOfSegments = 2;
71 m_activeControlPoint = -1;
75 m_pointContextMenu = new QMenu(this);
76 m_deleteAction = new QAction(tr("Delete point"), m_pointContextMenu);
77 m_smoothAction = new QAction(tr("Smooth point"), m_pointContextMenu);
78 m_cornerAction = new QAction(tr("Corner point"), m_pointContextMenu);
80 m_smoothAction->setCheckable(true);
82 m_pointContextMenu->addAction(m_deleteAction);
83 m_pointContextMenu->addAction(m_smoothAction);
84 m_pointContextMenu->addAction(m_cornerAction);
86 m_curveContextMenu = new QMenu(this);
88 m_addPoint = new QAction(tr("Add point"), m_pointContextMenu);
90 m_curveContextMenu->addAction(m_addPoint);
94 invalidateSmoothList();
97 static inline QPointF mapToCanvas(const QPointF &point)
99 return QPointF(point.x() * canvasWidth + canvasMargin,
100 canvasHeight - point.y() * canvasHeight + canvasMargin);
103 static inline QPointF mapFromCanvas(const QPointF &point)
105 return QPointF((point.x() - canvasMargin) / canvasWidth ,
106 1 - (point.y() - canvasMargin) / canvasHeight);
109 static inline void paintControlPoint(const QPointF &controlPoint, QPainter *painter, bool edit,
110 bool realPoint, bool active, bool smooth)
115 painter->setBrush(QColor(140, 140, 240, 255));
117 painter->setBrush(QColor(120, 120, 220, 255));
121 painter->setBrush(QColor(80, 80, 210, 150));
124 painter->setPen(QColor(50, 50, 50, 140));
127 painter->setBrush(QColor(160, 80, 80, 250));
130 painter->drawEllipse(QRectF(mapToCanvas(controlPoint).x() - pointSize + 0.5,
131 mapToCanvas(controlPoint).y() - pointSize + 0.5,
132 pointSize * 2, pointSize * 2));
134 painter->drawRect(QRectF(mapToCanvas(controlPoint).x() - pointSize + 0.5,
135 mapToCanvas(controlPoint).y() - pointSize + 0.5,
136 pointSize * 2, pointSize * 2));
140 static inline bool indexIsRealPoint(int i)
142 return !((i + 1) % 3);
145 static inline int pointForControlPoint(int i)
156 void drawCleanLine(QPainter *painter, const QPoint p1, QPoint p2)
158 painter->drawLine(p1 + QPointF(0.5 , 0.5), p2 + QPointF(0.5, 0.5));
161 void SplineEditor::paintEvent(QPaintEvent *)
163 QPainter painter(this);
167 painter.fillRect(0,0,width() - 1, height() - 1, QBrush(Qt::white));
168 painter.drawRect(0,0,width() - 1, height() - 1);
170 painter.setRenderHint(QPainter::Antialiasing);
172 pen = QPen(Qt::gray);
174 pen.setStyle(Qt::DashLine);
176 drawCleanLine(&painter,mapToCanvas(QPoint(0, 0)).toPoint(), mapToCanvas(QPoint(1, 0)).toPoint());
177 drawCleanLine(&painter,mapToCanvas(QPoint(0, 1)).toPoint(), mapToCanvas(QPoint(1, 1)).toPoint());
179 for (int i = 0; i < m_numberOfSegments; i++) {
184 p0 = mapToCanvas(QPointF(0.0, 0.0));
186 p0 = mapToCanvas(m_controlPoints.at(i * 3 - 1));
190 QPointF p1 = mapToCanvas(m_controlPoints.at(i * 3));
191 QPointF p2 = mapToCanvas(m_controlPoints.at(i * 3 + 1));
192 QPointF p3 = mapToCanvas(m_controlPoints.at(i * 3 + 2));
193 path.cubicTo(p1, p2, p3);
194 painter.strokePath(path, QPen(QBrush(Qt::black), 2));
198 pen.setStyle(Qt::DashLine);
200 painter.drawLine(p0, p1);
201 painter.drawLine(p3, p2);
204 paintControlPoint(QPointF(0.0, 0.0), &painter, false, true, false, false);
205 paintControlPoint(QPointF(1.0, 1.0), &painter, false, true, false, false);
207 for (int i = 0; i < m_controlPoints.count() - 1; ++i)
208 paintControlPoint(m_controlPoints.at(i),
212 i == m_activeControlPoint,
213 isControlPointSmooth(i));
216 void SplineEditor::mousePressEvent(QMouseEvent *e)
218 if (e->button() == Qt::LeftButton) {
219 m_activeControlPoint = findControlPoint(e->pos());
221 if (m_activeControlPoint != -1) {
224 m_mousePress = e->pos();
229 void SplineEditor::mouseReleaseEvent(QMouseEvent *e)
231 if (e->button() == Qt::LeftButton) {
232 m_activeControlPoint = -1;
239 void SplineEditor::contextMenuEvent(QContextMenuEvent *e)
241 int index = findControlPoint(e->pos());
243 if (index > 0 && indexIsRealPoint(index)) {
244 m_smoothAction->setChecked(isControlPointSmooth(index));
245 QAction* action = m_pointContextMenu->exec(e->globalPos());
246 if (action == m_deleteAction)
248 else if (action == m_smoothAction)
250 else if (action == m_cornerAction)
253 QAction* action = m_curveContextMenu->exec(e->globalPos());
254 if (action == m_addPoint)
259 void SplineEditor::invalidate()
261 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
263 for (int i = 0; i < m_numberOfSegments; ++i) {
264 easingCurve.addCubicBezierSegment(m_controlPoints.at(i * 3),
265 m_controlPoints.at(i * 3 + 1),
266 m_controlPoints.at(i * 3 + 2));
268 setEasingCurve(easingCurve);
269 invalidateSegmentProperties();
272 void SplineEditor::invalidateSmoothList()
274 m_smoothList.clear();
276 for (int i = 0; i < (m_numberOfSegments - 1); ++i)
277 m_smoothList.append(isSmooth(i * 3 + 2));
281 void SplineEditor::invalidateSegmentProperties()
283 for (int i = 0; i < m_numberOfSegments; ++i) {
284 SegmentProperties *segmentProperties = m_segmentProperties.at(i);
286 if (i < (m_numberOfSegments - 1)) {
287 smooth = m_smoothList.at(i);
289 segmentProperties->setSegment(i, m_controlPoints.mid(i * 3, 3), smooth, i == (m_numberOfSegments - 1));
293 QHash<QString, QEasingCurve> SplineEditor::presets() const
298 QString SplineEditor::generateCode()
300 QString s = QLatin1String("[");
301 foreach (const QPointF &point, m_controlPoints) {
302 s += QString::number(point.x(), 'g', 2) + "," + QString::number(point.y(), 'g', 3) + ",";
304 s.chop(1); //removing last ","
310 QStringList SplineEditor::presetNames() const
312 return m_presets.keys();
315 QWidget *SplineEditor::pointListWidget()
317 if (!m_pointListWidget) {
318 setupPointListWidget();
321 return m_pointListWidget;
324 int SplineEditor::findControlPoint(const QPoint &point)
328 for (int i = 0; i<m_controlPoints.size() - 1; ++i) {
329 qreal d = QLineF(point, mapToCanvas(m_controlPoints.at(i))).length();
330 if ((distance < 0 && d < 10) || d < distance) {
338 static inline bool veryFuzzyCompare(qreal r1, qreal r2)
340 if (qFuzzyCompare(r1, 2))
343 int r1i = qRound(r1 * 20);
344 int r2i = qRound(r2 * 20);
346 if (qFuzzyCompare(qreal(r1i) / 20, qreal(r2i) / 20))
352 bool SplineEditor::isSmooth(int i) const
357 QPointF p = m_controlPoints.at(i);
358 QPointF p_before = m_controlPoints.at(i - 1);
359 QPointF p_after = m_controlPoints.at(i + 1);
361 QPointF v1 = p_after - p;
362 v1 = v1 / v1.manhattanLength(); //normalize
364 QPointF v2 = p - p_before;
365 v2 = v2 / v2.manhattanLength(); //normalize
367 return veryFuzzyCompare(v1.x(), v2.x()) && veryFuzzyCompare(v1.y(), v2.y());
370 void SplineEditor::smoothPoint(int index)
372 if (m_smoothAction->isChecked()) {
374 QPointF before = QPointF(0,0);
376 before = m_controlPoints.at(index - 3);
378 QPointF after = QPointF(1.0, 1.0);
379 if ((index + 3) < m_controlPoints.count())
380 after = m_controlPoints.at(index + 3);
382 QPointF tangent = (after - before) / 6;
384 QPointF thisPoint = m_controlPoints.at(index);
387 m_controlPoints[index - 1] = thisPoint - tangent;
389 if (index + 1 < m_controlPoints.count())
390 m_controlPoints[index + 1] = thisPoint + tangent;
392 m_smoothList[index / 3] = true;
394 m_smoothList[index / 3] = false;
400 void SplineEditor::cornerPoint(int index)
402 QPointF before = QPointF(0,0);
404 before = m_controlPoints.at(index - 3);
406 QPointF after = QPointF(1.0, 1.0);
407 if ((index + 3) < m_controlPoints.count())
408 after = m_controlPoints.at(index + 3);
410 QPointF thisPoint = m_controlPoints.at(index);
413 m_controlPoints[index - 1] = (before - thisPoint) / 3 + thisPoint;
415 if (index + 1 < m_controlPoints.count())
416 m_controlPoints[index + 1] = (after - thisPoint) / 3 + thisPoint;
418 m_smoothList[(index) / 3] = false;
422 void SplineEditor::deletePoint(int index)
424 m_controlPoints.remove(index - 1, 3);
425 m_numberOfSegments--;
427 invalidateSmoothList();
428 setupPointListWidget();
432 void SplineEditor::addPoint(const QPointF point)
434 QPointF newPos = mapFromCanvas(point);
436 for (int i=0; i < m_controlPoints.size() - 1; ++i) {
437 if (indexIsRealPoint(i) && m_controlPoints.at(i).x() > newPos.x()) {
439 } else if (indexIsRealPoint(i))
442 QPointF before = QPointF(0,0);
444 before = m_controlPoints.at(splitIndex);
446 QPointF after = QPointF(1.0, 1.0);
447 if ((splitIndex + 3) < m_controlPoints.count())
448 after = m_controlPoints.at(splitIndex + 3);
450 if (splitIndex > 0) {
451 m_controlPoints.insert(splitIndex + 2, (newPos + after) / 2);
452 m_controlPoints.insert(splitIndex + 2, newPos);
453 m_controlPoints.insert(splitIndex + 2, (newPos + before) / 2);
455 m_controlPoints.insert(splitIndex + 1, (newPos + after) / 2);
456 m_controlPoints.insert(splitIndex + 1, newPos);
457 m_controlPoints.insert(splitIndex + 1, (newPos + before) / 2);
459 m_numberOfSegments++;
461 invalidateSmoothList();
462 setupPointListWidget();
466 void SplineEditor::initPresets()
468 const QPointF endPoint(1.0, 1.0);
470 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
471 easingCurve.addCubicBezierSegment(QPointF(0.4, 0.075), QPointF(0.45, 0.24), QPointF(0.5, 0.5));
472 easingCurve.addCubicBezierSegment(QPointF(0.55, 0.76), QPointF(0.7, 0.9), endPoint);
473 m_presets.insert(tr("Standard Easing"), easingCurve);
477 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
478 easingCurve.addCubicBezierSegment(QPointF(0.43, 0.0025), QPointF(0.65, 1), endPoint);
479 m_presets.insert(tr("Simple"), easingCurve);
483 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
484 easingCurve.addCubicBezierSegment(QPointF(0.43, 0.0025), QPointF(0.38, 0.51), QPointF(0.57, 0.99));
485 easingCurve.addCubicBezierSegment(QPointF(0.8, 0.69), QPointF(0.65, 1), endPoint);
486 m_presets.insert(tr("Simple Bounce"), easingCurve);
490 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
491 easingCurve.addCubicBezierSegment(QPointF(0.4, 0.075), QPointF(0.64, -0.0025), QPointF(0.74, 0.23));
492 easingCurve.addCubicBezierSegment(QPointF(0.84, 0.46), QPointF(0.91, 0.77), endPoint);
493 m_presets.insert(tr("Slow in fast out"), easingCurve);
497 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
498 easingCurve.addCubicBezierSegment(QPointF(0.43, 0.0025), QPointF(0.47, 0.51), QPointF(0.59, 0.94));
499 easingCurve.addCubicBezierSegment(QPointF(0.84, 0.95), QPointF( 0.99, 0.94), endPoint);
500 m_presets.insert(tr("Snapping"), easingCurve);
504 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
505 easingCurve.addCubicBezierSegment(QPointF( 0.38, 0.35),QPointF(0.38, 0.7), QPointF(0.45, 0.99));
506 easingCurve.addCubicBezierSegment(QPointF(0.48, 0.66), QPointF(0.62, 0.62), QPointF(0.66, 0.99));
507 easingCurve.addCubicBezierSegment(QPointF(0.69, 0.76), QPointF(0.77, 0.76), QPointF(0.79, 0.99));
508 easingCurve.addCubicBezierSegment(QPointF(0.83, 0.91), QPointF(0.87, 0.92), QPointF(0.91, 0.99));
509 easingCurve.addCubicBezierSegment(QPointF(0.95, 0.95), QPointF(0.97, 0.94), endPoint);
510 m_presets.insert(tr("Complex Bounce"), easingCurve);
514 QEasingCurve easingCurve4(QEasingCurve::BezierSpline);
515 easingCurve4.addCubicBezierSegment(QPointF(0.12, -0.12),QPointF(0.23, -0.19), QPointF( 0.35, -0.09));
516 easingCurve4.addCubicBezierSegment(QPointF(0.47, 0.005), QPointF(0.52, 1), QPointF(0.62, 1.1));
517 easingCurve4.addCubicBezierSegment(QPointF(0.73, 1.2), QPointF(0.91,1 ), endPoint);
518 m_presets.insert(tr("Overshoot"), easingCurve4);
522 void SplineEditor::setupPointListWidget()
524 if (!m_pointListWidget)
525 m_pointListWidget = new QScrollArea(this);
527 if (m_pointListWidget->widget())
528 delete m_pointListWidget->widget();
530 m_pointListWidget->setFrameStyle(QFrame::NoFrame);
531 m_pointListWidget->setWidgetResizable(true);
532 m_pointListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
534 m_pointListWidget->setWidget(new QWidget(m_pointListWidget));
535 QVBoxLayout *layout = new QVBoxLayout(m_pointListWidget->widget());
536 layout->setMargin(0);
537 layout->setSpacing(2);
538 m_pointListWidget->widget()->setLayout(layout);
540 m_segmentProperties.clear();
543 QWidget *widget = new QWidget(m_pointListWidget->widget());
545 pane.setupUi(widget);
546 pane.p1_x->setValue(0);
547 pane.p1_y->setValue(0);
548 layout->addWidget(widget);
549 pane.label->setText("p0");
550 widget->setEnabled(false);
553 for (int i = 0; i < m_numberOfSegments; ++i) {
554 SegmentProperties *segmentProperties = new SegmentProperties(m_pointListWidget->widget());
555 layout->addWidget(segmentProperties);
557 if (i < (m_numberOfSegments - 1)) {
558 smooth = m_smoothList.at(i);
560 segmentProperties->setSegment(i, m_controlPoints.mid(i * 3, 3), smooth, i == (m_numberOfSegments - 1));
561 segmentProperties->setSplineEditor(this);
562 m_segmentProperties << segmentProperties;
564 layout->addSpacerItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Expanding));
566 m_pointListWidget->viewport()->show();
567 m_pointListWidget->viewport()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
568 m_pointListWidget->show();
571 bool SplineEditor::isControlPointSmooth(int i) const
576 if (i == m_controlPoints.count() - 1)
579 if (m_numberOfSegments == 1)
582 int index = pointForControlPoint(i);
587 if (index == m_controlPoints.count() - 1)
590 return m_smoothList.at(index / 3);
593 QPointF limitToCanvas(const QPointF point)
595 qreal left = -qreal( canvasMargin) / qreal(canvasWidth);
596 qreal width = 1.0 - 2.0 * left;
598 qreal top = -qreal( canvasMargin) / qreal(canvasHeight);
599 qreal height = 1.0 - 2.0 * top;
602 QRectF r(left, top, width, height);
604 if (p.x() > r.right()) {
607 if (p.x() < r.left()) {
610 if (p.y() < r.top()) {
613 if (p.y() > r.bottom()) {
619 void SplineEditor::mouseMoveEvent(QMouseEvent *e)
621 // If we've moved more then 25 pixels, assume user is dragging
622 if (!m_mouseDrag && QPoint(m_mousePress - e->pos()).manhattanLength() > qApp->startDragDistance())
625 QPointF p = mapFromCanvas(e->pos());
627 if (m_mouseDrag && m_activeControlPoint >= 0 && m_activeControlPoint < m_controlPoints.size()) {
628 p = limitToCanvas(p);
629 if (indexIsRealPoint(m_activeControlPoint)) {
630 //move also the tangents
631 QPointF targetPoint = p;
632 QPointF distance = targetPoint - m_controlPoints[m_activeControlPoint];
633 m_controlPoints[m_activeControlPoint] = targetPoint;
634 m_controlPoints[m_activeControlPoint - 1] += distance;
635 m_controlPoints[m_activeControlPoint + 1] += distance;
637 if (!isControlPointSmooth(m_activeControlPoint)) {
638 m_controlPoints[m_activeControlPoint] = p;
640 QPointF targetPoint = p;
641 QPointF distance = targetPoint - m_controlPoints[m_activeControlPoint];
642 m_controlPoints[m_activeControlPoint] = p;
644 if ((m_activeControlPoint > 1) && (m_activeControlPoint % 3) == 0) { //right control point
645 m_controlPoints[m_activeControlPoint - 2] -= distance;
646 } else if ((m_activeControlPoint < (m_controlPoints.count() - 2)) //left control point
647 && (m_activeControlPoint % 3) == 1) {
648 m_controlPoints[m_activeControlPoint + 2] -= distance;
656 void SplineEditor::setEasingCurve(const QEasingCurve &easingCurve)
658 if (m_easingCurve == easingCurve)
661 m_easingCurve = easingCurve;
662 m_controlPoints = m_easingCurve.cubicBezierSpline().toVector();
663 m_numberOfSegments = m_controlPoints.count() / 3;
665 emit easingCurveChanged();
667 const QString code = generateCode();
668 emit easingCurveCodeChanged(code);
673 void SplineEditor::setPreset(const QString &name)
675 setEasingCurve(m_presets.value(name));
676 invalidateSmoothList();
677 setupPointListWidget();
680 void SplineEditor::setEasingCurve(const QString &code)
684 if (code.left(1) == QLatin1String("[") && code.right(1) == QLatin1String("]")) {
685 QString cleanCode = code;
686 cleanCode.remove(0, 1);
688 const QStringList stringList = cleanCode.split(QLatin1Char(','), QString::SkipEmptyParts);
689 if (stringList.count() >= 6 && (stringList.count() % 6 == 0)) {
690 QList<qreal> realList;
691 foreach (const QString &string, stringList) {
693 realList.append(string.toDouble(&ok));
697 QList<QPointF> points;
698 for (int i = 0; i < realList.count() / 2; ++i)
699 points.append(QPointF(realList.at(i * 2), realList.at(i * 2 + 1)));
700 if (points.last() == QPointF(1.0, 1.0)) {
701 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
703 for (int i = 0; i < points.count() / 3; ++i) {
704 easingCurve.addCubicBezierSegment(points.at(i * 3),
705 points.at(i * 3 + 1),
706 points.at(i * 3 + 2));
708 setEasingCurve(easingCurve);
709 invalidateSmoothList();
710 setupPointListWidget();