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

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