1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "splineeditor.h"
30#include "segmentproperties.h"
31
32#include <QPainter>
33#include <QPainterPath>
34#include <QMouseEvent>
35#include <QContextMenuEvent>
36#include <QDebug>
37#include <QApplication>
38#include <QVector>
39#include <QPainterPath>
40
41const int canvasWidth = 640;
42const int canvasHeight = 320;
43
44const int canvasMargin = 160;
45
46SplineEditor::SplineEditor(QWidget *parent) :
47 QWidget(parent), m_pointListWidget(nullptr), m_block(false)
48{
49 setFixedSize(w: canvasWidth + canvasMargin * 2, h: canvasHeight + canvasMargin * 2);
50
51 m_controlPoints.append(t: QPointF(0.4, 0.075));
52 m_controlPoints.append(t: QPointF(0.45,0.24));
53 m_controlPoints.append(t: QPointF(0.5,0.5));
54
55 m_controlPoints.append(t: QPointF(0.55,0.76));
56 m_controlPoints.append(t: QPointF(0.7,0.9));
57 m_controlPoints.append(t: QPointF(1.0, 1.0));
58
59 m_numberOfSegments = 2;
60
61 m_activeControlPoint = -1;
62
63 m_mouseDrag = false;
64
65 m_pointContextMenu = new QMenu(this);
66 m_deleteAction = new QAction(tr(s: "Delete point"), m_pointContextMenu);
67 m_smoothAction = new QAction(tr(s: "Smooth point"), m_pointContextMenu);
68 m_cornerAction = new QAction(tr(s: "Corner point"), m_pointContextMenu);
69
70 m_smoothAction->setCheckable(true);
71
72 m_pointContextMenu->addAction(action: m_deleteAction);
73 m_pointContextMenu->addAction(action: m_smoothAction);
74 m_pointContextMenu->addAction(action: m_cornerAction);
75
76 m_curveContextMenu = new QMenu(this);
77
78 m_addPoint = new QAction(tr(s: "Add point"), m_pointContextMenu);
79
80 m_curveContextMenu->addAction(action: m_addPoint);
81
82 initPresets();
83
84 invalidateSmoothList();
85}
86
87static inline QPointF mapToCanvas(const QPointF &point)
88{
89 return QPointF(point.x() * canvasWidth + canvasMargin,
90 canvasHeight - point.y() * canvasHeight + canvasMargin);
91}
92
93static inline QPointF mapFromCanvas(const QPointF &point)
94{
95 return QPointF((point.x() - canvasMargin) / canvasWidth ,
96 1 - (point.y() - canvasMargin) / canvasHeight);
97}
98
99static inline void paintControlPoint(const QPointF &controlPoint, QPainter *painter, bool edit,
100 bool realPoint, bool active, bool smooth)
101{
102 int pointSize = 4;
103
104 if (active)
105 painter->setBrush(QColor(140, 140, 240, 255));
106 else
107 painter->setBrush(QColor(120, 120, 220, 255));
108
109 if (realPoint) {
110 pointSize = 6;
111 painter->setBrush(QColor(80, 80, 210, 150));
112 }
113
114 painter->setPen(QColor(50, 50, 50, 140));
115
116 if (!edit)
117 painter->setBrush(QColor(160, 80, 80, 250));
118
119 if (smooth) {
120 painter->drawEllipse(r: QRectF(mapToCanvas(point: controlPoint).x() - pointSize + 0.5,
121 mapToCanvas(point: controlPoint).y() - pointSize + 0.5,
122 pointSize * 2, pointSize * 2));
123 } else {
124 painter->drawRect(rect: QRectF(mapToCanvas(point: controlPoint).x() - pointSize + 0.5,
125 mapToCanvas(point: controlPoint).y() - pointSize + 0.5,
126 pointSize * 2, pointSize * 2));
127 }
128}
129
130static inline bool indexIsRealPoint(int i)
131{
132 return !((i + 1) % 3);
133}
134
135static inline int pointForControlPoint(int i)
136{
137 if ((i % 3) == 0)
138 return i - 1;
139
140 if ((i % 3) == 1)
141 return i + 1;
142
143 return i;
144}
145
146void drawCleanLine(QPainter *painter, const QPoint p1, QPoint p2)
147{
148 painter->drawLine(p1: p1 + QPointF(0.5 , 0.5), p2: p2 + QPointF(0.5, 0.5));
149}
150
151void SplineEditor::paintEvent(QPaintEvent *)
152{
153 QPainter painter(this);
154
155 QPen pen(Qt::black);
156 pen.setWidth(1);
157 painter.fillRect(x: 0,y: 0,w: width() - 1, h: height() - 1, b: QBrush(Qt::white));
158 painter.drawRect(x: 0,y: 0,w: width() - 1, h: height() - 1);
159
160 painter.setRenderHint(hint: QPainter::Antialiasing);
161
162 pen = QPen(Qt::gray);
163 pen.setWidth(1);
164 pen.setStyle(Qt::DashLine);
165 painter.setPen(pen);
166 drawCleanLine(painter: &painter,p1: mapToCanvas(point: QPoint(0, 0)).toPoint(), p2: mapToCanvas(point: QPoint(1, 0)).toPoint());
167 drawCleanLine(painter: &painter,p1: mapToCanvas(point: QPoint(0, 1)).toPoint(), p2: mapToCanvas(point: QPoint(1, 1)).toPoint());
168
169 for (int i = 0; i < m_numberOfSegments; i++) {
170 QPainterPath path;
171 QPointF p0;
172
173 if (i == 0)
174 p0 = mapToCanvas(point: QPointF(0.0, 0.0));
175 else
176 p0 = mapToCanvas(point: m_controlPoints.at(i: i * 3 - 1));
177
178 path.moveTo(p: p0);
179
180 QPointF p1 = mapToCanvas(point: m_controlPoints.at(i: i * 3));
181 QPointF p2 = mapToCanvas(point: m_controlPoints.at(i: i * 3 + 1));
182 QPointF p3 = mapToCanvas(point: m_controlPoints.at(i: i * 3 + 2));
183 path.cubicTo(ctrlPt1: p1, ctrlPt2: p2, endPt: p3);
184 painter.strokePath(path, pen: QPen(QBrush(Qt::black), 2));
185
186 QPen pen(Qt::black);
187 pen.setWidth(1);
188 pen.setStyle(Qt::DashLine);
189 painter.setPen(pen);
190 painter.drawLine(p1: p0, p2: p1);
191 painter.drawLine(p1: p3, p2);
192 }
193
194 paintControlPoint(controlPoint: QPointF(0.0, 0.0), painter: &painter, edit: false, realPoint: true, active: false, smooth: false);
195 paintControlPoint(controlPoint: QPointF(1.0, 1.0), painter: &painter, edit: false, realPoint: true, active: false, smooth: false);
196
197 for (int i = 0; i < m_controlPoints.count() - 1; ++i)
198 paintControlPoint(controlPoint: m_controlPoints.at(i),
199 painter: &painter,
200 edit: true,
201 realPoint: indexIsRealPoint(i),
202 active: i == m_activeControlPoint,
203 smooth: isControlPointSmooth(i));
204}
205
206void SplineEditor::mousePressEvent(QMouseEvent *e)
207{
208 if (e->button() == Qt::LeftButton) {
209 m_activeControlPoint = findControlPoint(point: e->pos());
210
211 if (m_activeControlPoint != -1) {
212 mouseMoveEvent(e);
213 }
214 m_mousePress = e->pos();
215 e->accept();
216 }
217}
218
219void SplineEditor::mouseReleaseEvent(QMouseEvent *e)
220{
221 if (e->button() == Qt::LeftButton) {
222 m_activeControlPoint = -1;
223
224 m_mouseDrag = false;
225 e->accept();
226 }
227}
228
229#if QT_CONFIG(contextmenu)
230void SplineEditor::contextMenuEvent(QContextMenuEvent *e)
231{
232 int index = findControlPoint(point: e->pos());
233
234 if (index > 0 && indexIsRealPoint(i: index)) {
235 m_smoothAction->setChecked(isControlPointSmooth(i: index));
236 QAction* action = m_pointContextMenu->exec(pos: e->globalPos());
237 if (action == m_deleteAction)
238 deletePoint(index);
239 else if (action == m_smoothAction)
240 smoothPoint(index);
241 else if (action == m_cornerAction)
242 cornerPoint(index);
243 } else {
244 QAction* action = m_curveContextMenu->exec(pos: e->globalPos());
245 if (action == m_addPoint)
246 addPoint(point: e->pos());
247 }
248}
249#endif // contextmenu
250
251void SplineEditor::invalidate()
252{
253 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
254
255 for (int i = 0; i < m_numberOfSegments; ++i) {
256 easingCurve.addCubicBezierSegment(c1: m_controlPoints.at(i: i * 3),
257 c2: m_controlPoints.at(i: i * 3 + 1),
258 endPoint: m_controlPoints.at(i: i * 3 + 2));
259 }
260 setEasingCurve(easingCurve);
261 invalidateSegmentProperties();
262}
263
264void SplineEditor::invalidateSmoothList()
265{
266 m_smoothList.clear();
267
268 for (int i = 0; i < (m_numberOfSegments - 1); ++i)
269 m_smoothList.append(t: isSmooth(i: i * 3 + 2));
270
271}
272
273void SplineEditor::invalidateSegmentProperties()
274{
275 for (int i = 0; i < m_numberOfSegments; ++i) {
276 SegmentProperties *segmentProperties = m_segmentProperties.at(i);
277 bool smooth = false;
278 if (i < (m_numberOfSegments - 1)) {
279 smooth = m_smoothList.at(i);
280 }
281 segmentProperties->setSegment(segment: i, points: m_controlPoints.mid(pos: i * 3, len: 3), smooth, last: i == (m_numberOfSegments - 1));
282 }
283}
284
285QHash<QString, QEasingCurve> SplineEditor::presets() const
286{
287 return m_presets;
288}
289
290QString SplineEditor::generateCode()
291{
292 QString s = QLatin1String("[");
293 for (const QPointF &point : qAsConst(t&: m_controlPoints)) {
294 s += QString::number(point.x(), f: 'g', prec: 2) + QLatin1Char(',')
295 + QString::number(point.y(), f: 'g', prec: 3) + QLatin1Char(',');
296 }
297 s.chop(n: 1); //removing last ","
298 s += QLatin1Char(']');
299
300 return s;
301}
302
303QStringList SplineEditor::presetNames() const
304{
305 return m_presets.keys();
306}
307
308QWidget *SplineEditor::pointListWidget()
309{
310 if (!m_pointListWidget) {
311 setupPointListWidget();
312 }
313
314 return m_pointListWidget;
315}
316
317int SplineEditor::findControlPoint(const QPoint &point)
318{
319 int pointIndex = -1;
320 qreal distance = -1;
321 for (int i = 0; i<m_controlPoints.size() - 1; ++i) {
322 qreal d = QLineF(point, mapToCanvas(point: m_controlPoints.at(i))).length();
323 if ((distance < 0 && d < 10) || d < distance) {
324 distance = d;
325 pointIndex = i;
326 }
327 }
328 return pointIndex;
329}
330
331static inline bool veryFuzzyCompare(qreal r1, qreal r2)
332{
333 if (qFuzzyCompare(p1: r1, p2: 2))
334 return true;
335
336 int r1i = qRound(d: r1 * 20);
337 int r2i = qRound(d: r2 * 20);
338
339 if (qFuzzyCompare(p1: qreal(r1i) / 20, p2: qreal(r2i) / 20))
340 return true;
341
342 return false;
343}
344
345bool SplineEditor::isSmooth(int i) const
346{
347 if (i == 0)
348 return false;
349
350 QPointF p = m_controlPoints.at(i);
351 QPointF p_before = m_controlPoints.at(i: i - 1);
352 QPointF p_after = m_controlPoints.at(i: i + 1);
353
354 QPointF v1 = p_after - p;
355 v1 = v1 / v1.manhattanLength(); //normalize
356
357 QPointF v2 = p - p_before;
358 v2 = v2 / v2.manhattanLength(); //normalize
359
360 return veryFuzzyCompare(r1: v1.x(), r2: v2.x()) && veryFuzzyCompare(r1: v1.y(), r2: v2.y());
361}
362
363void SplineEditor::smoothPoint(int index)
364{
365 if (m_smoothAction->isChecked()) {
366
367 QPointF before = QPointF(0,0);
368 if (index > 3)
369 before = m_controlPoints.at(i: index - 3);
370
371 QPointF after = QPointF(1.0, 1.0);
372 if ((index + 3) < m_controlPoints.count())
373 after = m_controlPoints.at(i: index + 3);
374
375 QPointF tangent = (after - before) / 6;
376
377 QPointF thisPoint = m_controlPoints.at(i: index);
378
379 if (index > 0)
380 m_controlPoints[index - 1] = thisPoint - tangent;
381
382 if (index + 1 < m_controlPoints.count())
383 m_controlPoints[index + 1] = thisPoint + tangent;
384
385 m_smoothList[index / 3] = true;
386 } else {
387 m_smoothList[index / 3] = false;
388 }
389 invalidate();
390 update();
391}
392
393void SplineEditor::cornerPoint(int index)
394{
395 QPointF before = QPointF(0,0);
396 if (index > 3)
397 before = m_controlPoints.at(i: index - 3);
398
399 QPointF after = QPointF(1.0, 1.0);
400 if ((index + 3) < m_controlPoints.count())
401 after = m_controlPoints.at(i: index + 3);
402
403 QPointF thisPoint = m_controlPoints.at(i: index);
404
405 if (index > 0)
406 m_controlPoints[index - 1] = (before - thisPoint) / 3 + thisPoint;
407
408 if (index + 1 < m_controlPoints.count())
409 m_controlPoints[index + 1] = (after - thisPoint) / 3 + thisPoint;
410
411 m_smoothList[(index) / 3] = false;
412 invalidate();
413}
414
415void SplineEditor::deletePoint(int index)
416{
417 m_controlPoints.remove(i: index - 1, n: 3);
418 m_numberOfSegments--;
419
420 invalidateSmoothList();
421 setupPointListWidget();
422 invalidate();
423}
424
425void SplineEditor::addPoint(const QPointF point)
426{
427 QPointF newPos = mapFromCanvas(point);
428 int splitIndex = 0;
429 for (int i=0; i < m_controlPoints.size() - 1; ++i) {
430 if (indexIsRealPoint(i) && m_controlPoints.at(i).x() > newPos.x()) {
431 break;
432 } else if (indexIsRealPoint(i))
433 splitIndex = i;
434 }
435 QPointF before = QPointF(0,0);
436 if (splitIndex > 0)
437 before = m_controlPoints.at(i: splitIndex);
438
439 QPointF after = QPointF(1.0, 1.0);
440 if ((splitIndex + 3) < m_controlPoints.count())
441 after = m_controlPoints.at(i: splitIndex + 3);
442
443 if (splitIndex > 0) {
444 m_controlPoints.insert(i: splitIndex + 2, t: (newPos + after) / 2);
445 m_controlPoints.insert(i: splitIndex + 2, t: newPos);
446 m_controlPoints.insert(i: splitIndex + 2, t: (newPos + before) / 2);
447 } else {
448 m_controlPoints.insert(i: splitIndex + 1, t: (newPos + after) / 2);
449 m_controlPoints.insert(i: splitIndex + 1, t: newPos);
450 m_controlPoints.insert(i: splitIndex + 1, t: (newPos + before) / 2);
451 }
452 m_numberOfSegments++;
453
454 invalidateSmoothList();
455 setupPointListWidget();
456 invalidate();
457}
458
459void SplineEditor::initPresets()
460{
461 const QPointF endPoint(1.0, 1.0);
462 {
463 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
464 easingCurve.addCubicBezierSegment(c1: QPointF(0.4, 0.075), c2: QPointF(0.45, 0.24), endPoint: QPointF(0.5, 0.5));
465 easingCurve.addCubicBezierSegment(c1: QPointF(0.55, 0.76), c2: QPointF(0.7, 0.9), endPoint);
466 m_presets.insert(akey: tr(s: "Standard Easing"), avalue: easingCurve);
467 }
468
469 {
470 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
471 easingCurve.addCubicBezierSegment(c1: QPointF(0.43, 0.0025), c2: QPointF(0.65, 1), endPoint);
472 m_presets.insert(akey: tr(s: "Simple"), avalue: easingCurve);
473 }
474
475 {
476 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
477 easingCurve.addCubicBezierSegment(c1: QPointF(0.43, 0.0025), c2: QPointF(0.38, 0.51), endPoint: QPointF(0.57, 0.99));
478 easingCurve.addCubicBezierSegment(c1: QPointF(0.8, 0.69), c2: QPointF(0.65, 1), endPoint);
479 m_presets.insert(akey: tr(s: "Simple Bounce"), avalue: easingCurve);
480 }
481
482 {
483 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
484 easingCurve.addCubicBezierSegment(c1: QPointF(0.4, 0.075), c2: QPointF(0.64, -0.0025), endPoint: QPointF(0.74, 0.23));
485 easingCurve.addCubicBezierSegment(c1: QPointF(0.84, 0.46), c2: QPointF(0.91, 0.77), endPoint);
486 m_presets.insert(akey: tr(s: "Slow in fast out"), avalue: easingCurve);
487 }
488
489 {
490 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
491 easingCurve.addCubicBezierSegment(c1: QPointF(0.43, 0.0025), c2: QPointF(0.47, 0.51), endPoint: QPointF(0.59, 0.94));
492 easingCurve.addCubicBezierSegment(c1: QPointF(0.84, 0.95), c2: QPointF( 0.99, 0.94), endPoint);
493 m_presets.insert(akey: tr(s: "Snapping"), avalue: easingCurve);
494 }
495
496 {
497 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
498 easingCurve.addCubicBezierSegment(c1: QPointF( 0.38, 0.35),c2: QPointF(0.38, 0.7), endPoint: QPointF(0.45, 0.99));
499 easingCurve.addCubicBezierSegment(c1: QPointF(0.48, 0.66), c2: QPointF(0.62, 0.62), endPoint: QPointF(0.66, 0.99));
500 easingCurve.addCubicBezierSegment(c1: QPointF(0.69, 0.76), c2: QPointF(0.77, 0.76), endPoint: QPointF(0.79, 0.99));
501 easingCurve.addCubicBezierSegment(c1: QPointF(0.83, 0.91), c2: QPointF(0.87, 0.92), endPoint: QPointF(0.91, 0.99));
502 easingCurve.addCubicBezierSegment(c1: QPointF(0.95, 0.95), c2: QPointF(0.97, 0.94), endPoint);
503 m_presets.insert(akey: tr(s: "Complex Bounce"), avalue: easingCurve);
504 }
505
506 {
507 QEasingCurve easingCurve4(QEasingCurve::BezierSpline);
508 easingCurve4.addCubicBezierSegment(c1: QPointF(0.12, -0.12),c2: QPointF(0.23, -0.19), endPoint: QPointF( 0.35, -0.09));
509 easingCurve4.addCubicBezierSegment(c1: QPointF(0.47, 0.005), c2: QPointF(0.52, 1), endPoint: QPointF(0.62, 1.1));
510 easingCurve4.addCubicBezierSegment(c1: QPointF(0.73, 1.2), c2: QPointF(0.91,1 ), endPoint);
511 m_presets.insert(akey: tr(s: "Overshoot"), avalue: easingCurve4);
512 }
513}
514
515void SplineEditor::setupPointListWidget()
516{
517 if (!m_pointListWidget)
518 m_pointListWidget = new QScrollArea(this);
519
520 if (m_pointListWidget->widget())
521 delete m_pointListWidget->widget();
522
523 m_pointListWidget->setFrameStyle(QFrame::NoFrame);
524 m_pointListWidget->setWidgetResizable(true);
525 m_pointListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
526
527 m_pointListWidget->setWidget(new QWidget(m_pointListWidget));
528 QVBoxLayout *layout = new QVBoxLayout(m_pointListWidget->widget());
529 layout->setContentsMargins(QMargins());
530 layout->setSpacing(2);
531 m_pointListWidget->widget()->setLayout(layout);
532
533 m_segmentProperties.clear();
534
535 { //implicit 0,0
536 QWidget *widget = new QWidget(m_pointListWidget->widget());
537 Ui_Pane pane;
538 pane.setupUi(widget);
539 pane.p1_x->setValue(0);
540 pane.p1_y->setValue(0);
541 layout->addWidget(widget);
542 pane.label->setText("p0");
543 widget->setEnabled(false);
544 }
545
546 for (int i = 0; i < m_numberOfSegments; ++i) {
547 SegmentProperties *segmentProperties = new SegmentProperties(m_pointListWidget->widget());
548 layout->addWidget(segmentProperties);
549 bool smooth = false;
550 if (i < (m_numberOfSegments - 1)) {
551 smooth = m_smoothList.at(i);
552 }
553 segmentProperties->setSegment(segment: i, points: m_controlPoints.mid(pos: i * 3, len: 3), smooth, last: i == (m_numberOfSegments - 1));
554 segmentProperties->setSplineEditor(this);
555 m_segmentProperties << segmentProperties;
556 }
557 layout->addSpacerItem(spacerItem: new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Expanding));
558
559 m_pointListWidget->viewport()->show();
560 m_pointListWidget->viewport()->setSizePolicy(hor: QSizePolicy::Minimum, ver: QSizePolicy::Minimum);
561 m_pointListWidget->show();
562}
563
564bool SplineEditor::isControlPointSmooth(int i) const
565{
566 if (i == 0)
567 return false;
568
569 if (i == m_controlPoints.count() - 1)
570 return false;
571
572 if (m_numberOfSegments == 1)
573 return false;
574
575 int index = pointForControlPoint(i);
576
577 if (index == 0)
578 return false;
579
580 if (index == m_controlPoints.count() - 1)
581 return false;
582
583 return m_smoothList.at(i: index / 3);
584}
585
586QPointF limitToCanvas(const QPointF point)
587{
588 qreal left = -qreal( canvasMargin) / qreal(canvasWidth);
589 qreal width = 1.0 - 2.0 * left;
590
591 qreal top = -qreal( canvasMargin) / qreal(canvasHeight);
592 qreal height = 1.0 - 2.0 * top;
593
594 QPointF p = point;
595 QRectF r(left, top, width, height);
596
597 if (p.x() > r.right()) {
598 p.setX(r.right());
599 }
600 if (p.x() < r.left()) {
601 p.setX(r.left());
602 }
603 if (p.y() < r.top()) {
604 p.setY(r.top());
605 }
606 if (p.y() > r.bottom()) {
607 p.setY(r.bottom());
608 }
609 return p;
610}
611
612void SplineEditor::mouseMoveEvent(QMouseEvent *e)
613{
614 // If we've moved more then 25 pixels, assume user is dragging
615 if (!m_mouseDrag && QPoint(m_mousePress - e->pos()).manhattanLength() > qApp->startDragDistance())
616 m_mouseDrag = true;
617
618 QPointF p = mapFromCanvas(point: e->pos());
619
620 if (m_mouseDrag && m_activeControlPoint >= 0 && m_activeControlPoint < m_controlPoints.size()) {
621 p = limitToCanvas(point: p);
622 if (indexIsRealPoint(i: m_activeControlPoint)) {
623 //move also the tangents
624 QPointF targetPoint = p;
625 QPointF distance = targetPoint - m_controlPoints.at(i: m_activeControlPoint);
626 m_controlPoints[m_activeControlPoint] = targetPoint;
627 m_controlPoints[m_activeControlPoint - 1] += distance;
628 m_controlPoints[m_activeControlPoint + 1] += distance;
629 } else {
630 if (!isControlPointSmooth(i: m_activeControlPoint)) {
631 m_controlPoints[m_activeControlPoint] = p;
632 } else {
633 QPointF targetPoint = p;
634 QPointF distance = targetPoint - m_controlPoints.at(i: m_activeControlPoint);
635 m_controlPoints[m_activeControlPoint] = p;
636
637 if ((m_activeControlPoint > 1) && (m_activeControlPoint % 3) == 0) { //right control point
638 m_controlPoints[m_activeControlPoint - 2] -= distance;
639 } else if ((m_activeControlPoint < (m_controlPoints.count() - 2)) //left control point
640 && (m_activeControlPoint % 3) == 1) {
641 m_controlPoints[m_activeControlPoint + 2] -= distance;
642 }
643 }
644 }
645 invalidate();
646 }
647}
648
649void SplineEditor::setEasingCurve(const QEasingCurve &easingCurve)
650{
651 if (m_easingCurve == easingCurve)
652 return;
653 m_block = true;
654 m_easingCurve = easingCurve;
655 m_controlPoints = m_easingCurve.toCubicSpline();
656 m_numberOfSegments = m_controlPoints.count() / 3;
657 update();
658 emit easingCurveChanged();
659
660 const QString code = generateCode();
661 emit easingCurveCodeChanged(code);
662
663 m_block = false;
664}
665
666void SplineEditor::setPreset(const QString &name)
667{
668 setEasingCurve(m_presets.value(akey: name));
669 invalidateSmoothList();
670 setupPointListWidget();
671}
672
673void SplineEditor::setEasingCurve(const QString &code)
674{
675 if (m_block)
676 return;
677 if (code.startsWith(c: QLatin1Char('[')) && code.endsWith(c: QLatin1Char(']'))) {
678 const QStringRef cleanCode(&code, 1, code.size() - 2);
679 const auto stringList = cleanCode.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
680 if (stringList.count() >= 6 && (stringList.count() % 6 == 0)) {
681 QVector<qreal> realList;
682 realList.reserve(asize: stringList.count());
683 for (const QStringRef &string : stringList) {
684 bool ok;
685 realList.append(t: string.toDouble(ok: &ok));
686 if (!ok)
687 return;
688 }
689 QVector<QPointF> points;
690 const int count = realList.count() / 2;
691 points.reserve(asize: count);
692 for (int i = 0; i < count; ++i)
693 points.append(t: QPointF(realList.at(i: i * 2), realList.at(i: i * 2 + 1)));
694 if (points.constLast() == QPointF(1.0, 1.0)) {
695 QEasingCurve easingCurve(QEasingCurve::BezierSpline);
696
697 for (int i = 0; i < points.count() / 3; ++i) {
698 easingCurve.addCubicBezierSegment(c1: points.at(i: i * 3),
699 c2: points.at(i: i * 3 + 1),
700 endPoint: points.at(i: i * 3 + 2));
701 }
702 setEasingCurve(easingCurve);
703 invalidateSmoothList();
704 setupPointListWidget();
705 }
706 }
707 }
708}
709
710#include "moc_splineeditor.cpp"
711

source code of qtdeclarative/tools/qmleasing/splineeditor.cpp