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 | |
41 | const int canvasWidth = 640; |
42 | const int canvasHeight = 320; |
43 | |
44 | const int canvasMargin = 160; |
45 | |
46 | SplineEditor::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 | |
87 | static inline QPointF mapToCanvas(const QPointF &point) |
88 | { |
89 | return QPointF(point.x() * canvasWidth + canvasMargin, |
90 | canvasHeight - point.y() * canvasHeight + canvasMargin); |
91 | } |
92 | |
93 | static inline QPointF mapFromCanvas(const QPointF &point) |
94 | { |
95 | return QPointF((point.x() - canvasMargin) / canvasWidth , |
96 | 1 - (point.y() - canvasMargin) / canvasHeight); |
97 | } |
98 | |
99 | static 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 | |
130 | static inline bool indexIsRealPoint(int i) |
131 | { |
132 | return !((i + 1) % 3); |
133 | } |
134 | |
135 | static 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 | |
146 | void 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 | |
151 | void 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 | |
206 | void 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 | |
219 | void 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) |
230 | void SplineEditor::(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 | |
251 | void 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 | |
264 | void 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 | |
273 | void 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 | |
285 | QHash<QString, QEasingCurve> SplineEditor::presets() const |
286 | { |
287 | return m_presets; |
288 | } |
289 | |
290 | QString 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 | |
303 | QStringList SplineEditor::presetNames() const |
304 | { |
305 | return m_presets.keys(); |
306 | } |
307 | |
308 | QWidget *SplineEditor::pointListWidget() |
309 | { |
310 | if (!m_pointListWidget) { |
311 | setupPointListWidget(); |
312 | } |
313 | |
314 | return m_pointListWidget; |
315 | } |
316 | |
317 | int 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 | |
331 | static 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 | |
345 | bool 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 | |
363 | void 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 | |
393 | void 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 | |
415 | void 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 | |
425 | void 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 | |
459 | void 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 | |
515 | void 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 | |
564 | bool 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 | |
586 | QPointF 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 | |
612 | void 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 | |
649 | void 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 | |
666 | void SplineEditor::setPreset(const QString &name) |
667 | { |
668 | setEasingCurve(m_presets.value(akey: name)); |
669 | invalidateSmoothList(); |
670 | setupPointListWidget(); |
671 | } |
672 | |
673 | void 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 | |