1#include <Gui/Timeline/TimelineFrameSelector.hpp>
8#include <Core/Math/Math.hpp>
9#include <Gui/Timeline/TimelineScrollArea.hpp>
11#include "ui_Timeline.h"
15TimelineFrameSelector::TimelineFrameSelector( QWidget* parent ) : QFrame( parent ) {
16 m_timer =
new QTimer(
this );
17 connect( m_timer, SIGNAL( timeout() ),
this, SLOT( update() ) );
20TimelineFrameSelector::~TimelineFrameSelector() {
24void TimelineFrameSelector::setTimelineUi( Ui::Timeline* ui ) {
29 m_start = Scalar( m_timelineUI->m_startSpin->value() );
30 m_end = Scalar( m_timelineUI->m_endSpin->value() );
31 m_cursor = Scalar( m_timelineUI->m_cursorSpin->value() );
32 m_timelineUI->m_scrollArea->setMaxDuration( Scalar( m_timelineUI->m_durationSpin->value() ) );
35#define CAST_DOUBLE static_cast<void ( QDoubleSpinBox::* )( double )>
36 connect( m_timelineUI->m_startSpin,
37 CAST_DOUBLE( &QDoubleSpinBox::valueChanged ),
38 [
this](
double start ) { onChangeStart( Scalar( start ) ); } );
39 connect( m_timelineUI->m_cursorSpin,
40 CAST_DOUBLE( &QDoubleSpinBox::valueChanged ),
41 [
this](
double cursor ) { onChangeCursor( Scalar( cursor ) ); } );
42 connect( m_timelineUI->m_endSpin,
43 CAST_DOUBLE( &QDoubleSpinBox::valueChanged ),
44 [
this](
double end ) { onChangeEnd( Scalar( end ) ); } );
45 connect( m_timelineUI->m_durationSpin,
46 CAST_DOUBLE( &QDoubleSpinBox::valueChanged ),
47 [
this](
double duration ) { onChangeDuration( Scalar( duration ) ); } );
50 connect( m_timelineUI->m_leftSlider,
51 &TimelineSlider::slide,
53 &TimelineFrameSelector::onSlideLeftSlider );
54 connect( m_timelineUI->m_rightSlider,
55 &TimelineSlider::slide,
57 &TimelineFrameSelector::onSlideRightSlider );
59 connect( m_timelineUI->toolButton_start,
60 &QToolButton::clicked,
62 &TimelineFrameSelector::onSetCursorToStart );
63 connect( m_timelineUI->toolButton_rearward,
64 &QToolButton::clicked,
66 &TimelineFrameSelector::onSetCursorToPreviousKeyFrame );
67 connect( m_timelineUI->toolButton_forward,
68 &QToolButton::clicked,
70 &TimelineFrameSelector::onSetCursorToNextKeyFrame );
71 connect( m_timelineUI->toolButton_end,
72 &QToolButton::clicked,
74 &TimelineFrameSelector::onSetCursorToEnd );
77 m_timelineUI->toolButton_keyFrame, SIGNAL( clicked() ),
this, SLOT( onAddingKeyFrame() ) );
78 connect( m_timelineUI->m_removeKeyFrameButton,
81 SLOT( onDeletingKeyFrame() ) );
84 m_timelineUI->m_scrollArea, SIGNAL( addKeyFrame() ),
this, SLOT( onAddingKeyFrame() ) );
85 connect( m_timelineUI->m_scrollArea,
86 SIGNAL( removeKeyFrame() ),
88 SLOT( onDeletingKeyFrame() ) );
89 connect( m_timelineUI->m_scrollArea,
90 &TimelineScrollArea::nextKeyFrame,
92 &TimelineFrameSelector::onSetCursorToNextKeyFrame );
93 connect( m_timelineUI->m_scrollArea,
94 &TimelineScrollArea::previousKeyFrame,
96 &TimelineFrameSelector::onSetCursorToPreviousKeyFrame );
97 connect( m_timelineUI->m_scrollArea,
98 &TimelineScrollArea::togglePlayPause,
99 m_timelineUI->toolButton_playPause,
100 &QToolButton::toggle );
101 connect( m_timelineUI->m_scrollArea, &TimelineScrollArea::durationIncrement, [
this]() {
102 onChangeDuration( Scalar( m_timelineUI->m_durationSpin->value() +
103 m_timelineUI->m_durationSpin->singleStep() ) );
105 connect( m_timelineUI->m_scrollArea, &TimelineScrollArea::durationDecrement, [
this]() {
106 onChangeDuration( Scalar( m_timelineUI->m_durationSpin->value() -
107 m_timelineUI->m_durationSpin->singleStep() ) );
109 connect( m_timelineUI->m_scrollArea, &TimelineScrollArea::stepChanged, [
this](
double step ) {
110 m_timelineUI->m_startSpin->setSingleStep( 0.5 * step );
111 m_timelineUI->m_endSpin->setSingleStep( 0.5 * step );
112 m_timelineUI->m_cursorSpin->setSingleStep( 0.5 * step );
113 m_timelineUI->m_durationSpin->setSingleStep( 0.5 * step );
117void TimelineFrameSelector::onChangeStart( Scalar time,
bool internal ) {
120 bool out = std::abs( newStart - time ) > 1e-5_ra;
121 bool change = std::abs( newStart - m_start ) > 1e-5_ra;
129 if ( internal || out ) { emit startChanged( m_start ); }
132 if ( out ) { updateStartSpin(); }
136void TimelineFrameSelector::onChangeEnd( Scalar time,
bool internal ) {
138 std::min(
std::max( time, m_start ), m_timelineUI->m_scrollArea->getMaxDuration() );
140 bool out = std::abs( newEnd - time ) > 1e-5_ra;
141 bool change = std::abs( newEnd - m_end ) > 1e-5_ra;
147 if ( internal || out ) { emit endChanged( m_end ); }
151 if ( out ) { updateEndSpin(); }
155void TimelineFrameSelector::onChangeDuration( Scalar time,
bool internal ) {
156 Scalar newDuration =
std::max( time, 0_ra );
157 Scalar oldDuration = m_timelineUI->m_scrollArea->getMaxDuration();
159 bool out = std::abs( newDuration - time ) > 1e-5_ra;
160 bool change = std::abs( newDuration - oldDuration ) > 1e-5_ra;
163 oldDuration = newDuration;
164 m_timelineUI->m_scrollArea->setMaxDuration( newDuration );
165 m_timelineUI->m_scrollArea->onDrawRuler(
166 m_timelineUI->m_scrollArea->widget()->minimumWidth() );
167 updateDurationSpin();
170 if ( internal || out ) { emit durationChanged( newDuration ); }
173 if ( out ) { updateDurationSpin(); }
176 if ( oldDuration < m_start ) onChangeStart( oldDuration );
178 if ( oldDuration < m_end ) onChangeEnd( oldDuration );
183void TimelineFrameSelector::onChangeCursor( Scalar time,
bool internal ) {
184 Scalar newCursor =
std::max( 0_ra, time );
186 if ( internal ) { newCursor = nearestStep( newCursor ); }
188 bool out = std::abs( newCursor - time ) > 1e-5_ra;
189 bool change = std::abs( newCursor - m_cursor ) > 1e-5_ra;
192 m_cursor = newCursor;
194 if ( internal || out ) { emit cursorChanged( m_cursor ); }
198 if ( out ) { updateCursorSpin(); }
202void TimelineFrameSelector::onAddingKeyFrame( Scalar time,
bool internal ) {
204 if (
static_cast<int>( time ) == -1 ) time = m_cursor;
206 auto it =
std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [time](
const auto& t ) {
207 return Ra::Core::Math::areApproxEqual( t, time );
211 if ( it == m_keyFrames.end() ) {
214 m_keyFrames.push_back( time );
215 std::sort( m_keyFrames.begin(), m_keyFrames.end() );
217 if ( internal ) { emit keyFrameAdded( time ); }
219 updateNbKeyFrameSpin();
225 if ( internal ) { emit keyFrameChanged(
std::distance( m_keyFrames.begin(), it ) ); }
227 m_updateKeyFrameFlash = 6;
228 m_keyFrameFlash = time;
230 m_timer->start( 50 );
236void TimelineFrameSelector::onDeletingKeyFrame(
bool internal ) {
237 auto it =
std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [
this](
const auto& t ) {
238 return Ra::Core::Math::areApproxEqual( t, m_cursor );
240 if ( it == m_keyFrames.end() ) {
return; }
242 if ( internal ) { emit keyFrameDeleted(
std::distance( m_keyFrames.begin(), it ) ); }
244 m_keyFrames.erase( it );
248 updateNbKeyFrameSpin();
253void TimelineFrameSelector::onMoveKeyFrame( Scalar time0, Scalar time1,
bool internal ) {
256 auto it =
std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [
this](
const auto& t ) {
257 return Ra::Core::Math::areApproxEqual( t, m_cursor );
259 if ( it == m_keyFrames.end() ) {
return; }
261 if ( internal ) { emit keyFrameMoved(
std::distance( m_keyFrames.begin(), it ), time1 ); }
265 m_keyFrames.erase( it );
266 m_keyFrames.push_back( m_cursor );
267 std::sort( m_keyFrames.begin(), m_keyFrames.end() );
274void TimelineFrameSelector::onMoveKeyFrames( Scalar time, Scalar offset,
bool internal ) {
277 auto it =
std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [time](
const auto& t ) {
278 return Ra::Core::Math::areApproxEqual( t, time );
280 if ( it == m_keyFrames.end() ) {
return; }
283 if ( internal ) { emit keyFramesMoved(
std::distance( m_keyFrames.begin(), it ), offset ); }
289 [time, offset]( Scalar p ) { return ( p < time ? p : p + offset ); } );
290 std::exchange( m_keyFrames, clone );
292 Scalar left = ( offset > 0 ) ? ( time ) : ( time + offset );
294 if ( m_start >= left ) {
296 std::min( m_start + offset, m_timelineUI->m_scrollArea->getMaxDuration() ), 0_ra );
298 if ( internal ) { emit startChanged( m_start ); }
301 Scalar right = *m_keyFrames.rbegin();
302 if ( right > m_timelineUI->m_scrollArea->getMaxDuration() ) {
303 m_timelineUI->m_scrollArea->setMaxDuration( right );
304 updateDurationSpin();
305 if ( internal ) { emit durationChanged( right ); }
308 if ( m_end >= left ) {
309 m_end = m_end + offset;
311 if ( internal ) { emit endChanged( m_end ); }
314 if ( m_cursor >= left ) {
315 m_cursor =
std::max( m_cursor + offset, 0_ra );
318 if ( internal ) { emit cursorChanged( m_cursor ); }
324void TimelineFrameSelector::onClearKeyFrames() {
326 updateNbKeyFrameSpin();
333void TimelineFrameSelector::onSlideLeftSlider(
int deltaX ) {
334 { m_timelineUI->m_leftSlider->setStyleSheet(
"background-color: green" ); }
336 Scalar pixPerSec = m_timelineUI->m_scrollArea->getPixPerSec();
337 Scalar newStart = m_start + deltaX / pixPerSec;
339 onChangeStart( newStart );
342void TimelineFrameSelector::onSlideRightSlider(
int deltaX ) {
343 { m_timelineUI->m_rightSlider->setStyleSheet(
"background-color: red" ); }
344 Scalar pixPerSec = m_timelineUI->m_scrollArea->getPixPerSec();
345 Scalar newEnd = m_end + deltaX / pixPerSec;
347 onChangeEnd( newEnd );
350void TimelineFrameSelector::onSetCursorToStart() {
351 onChangeCursor( m_start );
354void TimelineFrameSelector::onSetCursorToEnd() {
355 onChangeCursor( m_end );
358void TimelineFrameSelector::onSetCursorToPreviousKeyFrame() {
359 auto it = m_keyFrames.rbegin();
360 while ( it != m_keyFrames.rend() && *it >= m_cursor )
363 if ( it != m_keyFrames.rend() ) { onChangeCursor( *it ); }
366void TimelineFrameSelector::onSetCursorToNextKeyFrame() {
367 auto it = m_keyFrames.begin();
368 while ( it != m_keyFrames.end() && *it <= m_cursor )
371 if ( it != m_keyFrames.end() ) { onChangeCursor( *it ); }
374void TimelineFrameSelector::paintEvent( QPaintEvent* ) {
376 QPainter painter(
this );
383 painter.setPen( QPen( Qt::lightGray ) );
384 Scalar frameDuration = 1_ra / TIMELINE_FPS;
386 Scalar nbFrame = m_timelineUI->m_scrollArea->getMaxDuration() / frameDuration;
387 Scalar pixPerSec = m_timelineUI->m_scrollArea->getPixPerSec();
388 Scalar step = m_timelineUI->m_scrollArea->getStep();
389 int zero = m_timelineUI->m_scrollArea->getZero();
390 for (
int i = 0; i < nbFrame; i++ ) {
391 int x = int( i * frameDuration * pixPerSec + zero );
392 painter.drawLine( x, 0, x, hUp );
396 painter.setPen( QPen( QColor( 0, 0, 255, 255 ), 3 ) );
397 int xCursor = int( zero + m_cursor * pixPerSec );
398 painter.drawLine( xCursor, 0, xCursor, h );
401 painter.setPen( QPen( QColor( 255, 255, 0, 255 ), 3 ) );
402 int hTemp = h / 3 + 2;
403 for ( Scalar keyFrame : m_keyFrames ) {
404 int xKeyFrame = int( zero + keyFrame * pixPerSec );
405 painter.drawLine( xKeyFrame, hTemp, xKeyFrame, h );
409 int hDown = 2 * h / 3;
410 painter.setPen( Qt::black );
411 for (
int i = 1; i < m_timelineUI->m_scrollArea->getNbInterval(); i++ ) {
412 int x = int( i * step * pixPerSec );
413 painter.drawLine( x, hDown, x, h );
415 int hDown2 = 3 * h / 4;
416 painter.setPen( Qt::darkGray );
417 for (
int i = 1; i < m_timelineUI->m_scrollArea->getNbInterval() - 1; i++ ) {
418 int middle = int( ( i + 0.5_ra ) * step * pixPerSec );
419 painter.drawLine( middle, hDown2, middle, h );
422 if ( m_updateKeyFrameFlash > 0 ) {
424 if ( m_updateKeyFrameFlash % 2 == 0 ) {
425 painter.setPen( QPen( QColor( 0, 0, 255, 255 ), 3 ) );
426 int xKeyFrame =
static_cast<int>( zero + m_keyFrameFlash * pixPerSec );
427 painter.drawLine( xKeyFrame, hTemp, xKeyFrame, h );
430 if ( --m_updateKeyFrameFlash == 0 ) m_timer->stop();
435 painter.setPen( Qt::white );
436 painter.drawLine( 0, h / 2 + gap + 2, w, h / 2 + gap + 2 );
437 painter.drawLine( 0, h / 2 + gap + 1, w, h / 2 + gap + 1 );
439 painter.setPen( Qt::darkGray );
440 painter.drawLine( 0, h / 3, w, h / 3 );
441 painter.drawLine( 0, h / 2 + gap, w, h / 2 + gap );
444void TimelineFrameSelector::mousePressEvent( QMouseEvent* event ) {
445 bool shiftDown =
event->modifiers() & Qt::Modifier::SHIFT;
446 bool ctrlDown =
event->modifiers() & Qt::Modifier::CTRL;
448 if ( event->button() == Qt::LeftButton ) {
449 Scalar newCursor =
std::max( Scalar( event->x() - m_timelineUI->m_scrollArea->getZero() ) /
450 m_timelineUI->m_scrollArea->getPixPerSec(),
453 if ( ctrlDown ) { onChangeCursor( newCursor,
false ); }
455 else if ( shiftDown ) { deleteZone( m_cursor, newCursor ); }
458 onChangeCursor( newCursor );
459 m_mouseLeftClicked =
true;
463 else if ( event->button() == Qt::RightButton ) {
464 Scalar newFrame =
std::max( Scalar( event->x() - m_timelineUI->m_scrollArea->getZero() ) /
465 m_timelineUI->m_scrollArea->getPixPerSec(),
467 auto it =
std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [
this](
const auto& t ) {
468 return Ra::Core::Math::areApproxEqual( t, m_cursor );
472 if ( it != m_keyFrames.end() ) {
473 Scalar nearest = nearestStep( newFrame );
478 m_keyFrames.begin(), m_keyFrames.end(), [nearest](
const auto& t ) {
479 return Ra::Core::Math::areApproxEqual( t, nearest );
481 if ( it2 == m_keyFrames.end() && ( std::abs( m_cursor - nearest ) > 1e-5_ra ) ) {
482 onMoveKeyFrame( m_cursor, nearest );
490 Scalar
left = ( it == m_keyFrames.begin() ) ? ( 0.0 ) : ( *itLeft );
491 if ( nearest > left ) { onMoveKeyFrames( m_cursor, nearest - m_cursor ); }
499 auto itRight =
std::lower_bound( m_keyFrames.begin(), m_keyFrames.end(), newFrame );
501 if ( itRight != m_keyFrames.end() ) {
502 onMoveKeyFrames( *itRight, newFrame - *itRight );
511 if ( itLeft != m_keyFrames.end() ) {
512 onMoveKeyFrames( *itLeft, newFrame - *itLeft );
525void TimelineFrameSelector::mouseMoveEvent( QMouseEvent* event ) {
526 if ( m_mouseLeftClicked ) {
527 Scalar newCursor =
std::max( Scalar( event->x() - m_timelineUI->m_scrollArea->getZero() ) /
528 m_timelineUI->m_scrollArea->getPixPerSec(),
531 onChangeCursor( newCursor );
533 else {
event->ignore(); }
536void TimelineFrameSelector::mouseReleaseEvent( QMouseEvent* event ) {
537 if ( event->button() == Qt::LeftButton ) {
538 m_mouseLeftClicked =
false;
541 else {
event->ignore(); }
544void TimelineFrameSelector::updateCursorSpin() {
545 auto it =
std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [
this](
const auto& t ) {
546 return Ra::Core::Math::areApproxEqual( t, m_cursor );
548 if ( it != m_keyFrames.end() ) {
549 m_timelineUI->m_cursorSpin->setStyleSheet(
"background-color: yellow" );
550 m_timelineUI->m_removeKeyFrameButton->setEnabled(
true );
553 m_timelineUI->m_cursorSpin->setStyleSheet(
"background-color: #5555ff" );
554 m_timelineUI->m_removeKeyFrameButton->setEnabled(
false );
556 m_timelineUI->m_cursorSpin->setValue(
double( m_cursor ) );
559void TimelineFrameSelector::updateStartSpin() {
560 m_timelineUI->m_startSpin->setValue(
double( m_start ) );
561 updateDurationSpin();
564void TimelineFrameSelector::updateEndSpin() {
565 m_timelineUI->m_endSpin->setValue(
double( m_end ) );
566 updateDurationSpin();
569void TimelineFrameSelector::updateDurationSpin() {
570 m_timelineUI->m_durationSpin->setValue(
571 double( m_timelineUI->m_scrollArea->getMaxDuration() ) );
574void TimelineFrameSelector::updateNbKeyFrameSpin() {
575 m_timelineUI->m_nbKeyFramesSpin->setText(
" " + QString::number( m_keyFrames.size() ) +
" " );
578Scalar TimelineFrameSelector::nearestStep( Scalar time )
const {
580 TIMELINE_AUTO_SUGGEST_CURSOR_RADIUS / m_timelineUI->m_scrollArea->getPixPerSec();
582 Scalar minDist = Scalar( m_timelineUI->m_durationSpin->maximum() );
585 Scalar newCursor =
time;
587 for ( Scalar keyFrame : m_keyFrames ) {
588 dist = qAbs( keyFrame - time );
589 if ( dist < deltaT && dist < minDist ) {
591 newCursor = keyFrame;
593 if ( time < keyFrame )
break;
596 Scalar step = m_timelineUI->m_scrollArea->getStep();
597 for (
int i = 0; i < m_timelineUI->m_scrollArea->getNbInterval() - 1; ++i ) {
598 Scalar pos = i * step;
599 dist = std::abs( pos - time );
600 if ( dist < deltaT && dist < minDist ) {
605 pos = i * step + 0.5_ra * step;
606 dist = std::abs( pos - time );
607 if ( dist < deltaT && dist < minDist ) {
612 if ( time < pos )
break;
618void TimelineFrameSelector::deleteZone( Scalar time, Scalar time2 ) {
625 auto it = m_keyFrames.begin();
627 while ( it != m_keyFrames.end() ) {
628 Scalar keyFrame = *it;
630 if ( keyFrame >= left ) {
631 it = m_keyFrames.erase( it );
633 if ( keyFrame > right ) {
635 emit keyFramesMoved(
std::distance( m_keyFrames.begin(), it ), -dist );
638 it = m_keyFrames.insert( it, keyFrame - dist );
641 else { emit keyFrameDeleted(
std::distance( m_keyFrames.begin(), it ) ); }
645 updateNbKeyFrameSpin();
649 if ( std::abs( newStart - m_start ) > 1e-5_ra ) {
653 emit startChanged( m_start );
657 if ( std::abs( newEnd - m_end ) > 1e-5_ra ) {
661 emit endChanged( m_end );
664 emit cursorChanged( m_cursor );
669void TimelineFrameSelector::redrawPlayZone() {
670 m_timelineUI->m_leftSpacer->setMinimumWidth(
671 int( m_timelineUI->m_scrollArea->getZero() +
672 m_start * m_timelineUI->m_scrollArea->getPixPerSec() -
673 m_timelineUI->m_leftSlider->width() ) );
674 m_timelineUI->m_playZone->setMinimumWidth(
675 int( ( m_end - m_start ) * m_timelineUI->m_scrollArea->getPixPerSec() ) );
T back_inserter(T... args)
std::enable_if<!std::numeric_limits< T >::is_integer, bool >::type areApproxEqual(T x, T y, T espilonBoostFactor=T(10))
Compare two numbers such that |x-y| < espilon*epsilonBoostFactor.