Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
TimelineFrameSelector.cpp
1 #include <Gui/Timeline/TimelineFrameSelector.hpp>
2 
3 #include <QMouseEvent>
4 #include <QPainter>
5 #include <QTimer>
6 #include <QtGlobal>
7 
8 #include <Core/Math/Math.hpp>
9 #include <Gui/Timeline/TimelineScrollArea.hpp>
10 
11 #include "ui_Timeline.h"
12 
13 namespace Ra::Gui {
14 
15 TimelineFrameSelector::TimelineFrameSelector( QWidget* parent ) : QFrame( parent ) {
16  m_timer = new QTimer( this );
17  connect( m_timer, SIGNAL( timeout() ), this, SLOT( update() ) );
18 }
19 
20 TimelineFrameSelector::~TimelineFrameSelector() {
21  delete m_timer;
22 }
23 
24 void TimelineFrameSelector::setTimelineUi( Ui::Timeline* ui ) {
25  // register UI
26  m_timelineUI = ui;
27 
28  // init values from 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() ) );
33 
34  // create connections
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 ) ); } );
48 #undef CAST_DOUBLE
49 
50  connect( m_timelineUI->m_leftSlider,
51  &TimelineSlider::slide,
52  this,
53  &TimelineFrameSelector::onSlideLeftSlider );
54  connect( m_timelineUI->m_rightSlider,
55  &TimelineSlider::slide,
56  this,
57  &TimelineFrameSelector::onSlideRightSlider );
58 
59  connect( m_timelineUI->toolButton_start,
60  &QToolButton::clicked,
61  this,
62  &TimelineFrameSelector::onSetCursorToStart );
63  connect( m_timelineUI->toolButton_rearward,
64  &QToolButton::clicked,
65  this,
66  &TimelineFrameSelector::onSetCursorToPreviousKeyFrame );
67  connect( m_timelineUI->toolButton_forward,
68  &QToolButton::clicked,
69  this,
70  &TimelineFrameSelector::onSetCursorToNextKeyFrame );
71  connect( m_timelineUI->toolButton_end,
72  &QToolButton::clicked,
73  this,
74  &TimelineFrameSelector::onSetCursorToEnd );
75 
76  connect(
77  m_timelineUI->toolButton_keyFrame, SIGNAL( clicked() ), this, SLOT( onAddingKeyFrame() ) );
78  connect( m_timelineUI->m_removeKeyFrameButton,
79  SIGNAL( clicked() ),
80  this,
81  SLOT( onDeletingKeyFrame() ) );
82 
83  connect(
84  m_timelineUI->m_scrollArea, SIGNAL( addKeyFrame() ), this, SLOT( onAddingKeyFrame() ) );
85  connect( m_timelineUI->m_scrollArea,
86  SIGNAL( removeKeyFrame() ),
87  this,
88  SLOT( onDeletingKeyFrame() ) );
89  connect( m_timelineUI->m_scrollArea,
90  &TimelineScrollArea::nextKeyFrame,
91  this,
92  &TimelineFrameSelector::onSetCursorToNextKeyFrame );
93  connect( m_timelineUI->m_scrollArea,
94  &TimelineScrollArea::previousKeyFrame,
95  this,
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() ) );
104  } );
105  connect( m_timelineUI->m_scrollArea, &TimelineScrollArea::durationDecrement, [this]() {
106  onChangeDuration( Scalar( m_timelineUI->m_durationSpin->value() -
107  m_timelineUI->m_durationSpin->singleStep() ) );
108  } );
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 );
114  } );
115 }
116 
117 void TimelineFrameSelector::onChangeStart( Scalar time, bool internal ) {
118  Scalar newStart = std::max( std::min( time, m_end ), 0_ra );
119 
120  bool out = std::abs( newStart - time ) > 1e-5_ra;
121  bool change = std::abs( newStart - m_start ) > 1e-5_ra;
122 
123  if ( change ) {
124  m_start = newStart;
125  updateStartSpin();
126  redrawPlayZone();
127 
128  // emit signal if time of emitter is internal changed due of limits
129  if ( internal || out ) { emit startChanged( m_start ); }
130  }
131  else {
132  if ( out ) { updateStartSpin(); }
133  }
134 }
135 
136 void TimelineFrameSelector::onChangeEnd( Scalar time, bool internal ) {
137  Scalar newEnd =
138  std::min( std::max( time, m_start ), m_timelineUI->m_scrollArea->getMaxDuration() );
139 
140  bool out = std::abs( newEnd - time ) > 1e-5_ra;
141  bool change = std::abs( newEnd - m_end ) > 1e-5_ra;
142 
143  // emit signal only if new value of end
144  if ( change ) {
145  m_end = newEnd;
146  updateEndSpin();
147  if ( internal || out ) { emit endChanged( m_end ); }
148  update();
149  }
150  else {
151  if ( out ) { updateEndSpin(); }
152  }
153 }
154 
155 void TimelineFrameSelector::onChangeDuration( Scalar time, bool internal ) {
156  Scalar newDuration = std::max( time, 0_ra );
157  Scalar oldDuration = m_timelineUI->m_scrollArea->getMaxDuration();
158 
159  bool out = std::abs( newDuration - time ) > 1e-5_ra;
160  bool change = std::abs( newDuration - oldDuration ) > 1e-5_ra;
161 
162  if ( change ) {
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();
168 
169  // emit signal if time of emitter is internal changed due of limits
170  if ( internal || out ) { emit durationChanged( newDuration ); }
171  }
172  else {
173  if ( out ) { updateDurationSpin(); }
174  }
175 
176  if ( oldDuration < m_start ) onChangeStart( oldDuration );
177 
178  if ( oldDuration < m_end ) onChangeEnd( oldDuration );
179 
180  // auto update
181 }
182 
183 void TimelineFrameSelector::onChangeCursor( Scalar time, bool internal ) {
184  Scalar newCursor = std::max( 0_ra, time );
185 
186  if ( internal ) { newCursor = nearestStep( newCursor ); }
187 
188  bool out = std::abs( newCursor - time ) > 1e-5_ra;
189  bool change = std::abs( newCursor - m_cursor ) > 1e-5_ra;
190 
191  if ( change ) {
192  m_cursor = newCursor;
193  updateCursorSpin();
194  if ( internal || out ) { emit cursorChanged( m_cursor ); }
195  update();
196  }
197  else {
198  if ( out ) { updateCursorSpin(); }
199  }
200 }
201 
202 void TimelineFrameSelector::onAddingKeyFrame( Scalar time, bool internal ) {
203  // by default (time = -1.0), add keyFrame on cursor
204  if ( static_cast<int>( time ) == -1 ) time = m_cursor;
205 
206  auto it = std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [time]( const auto& t ) {
207  return Ra::Core::Math::areApproxEqual( t, time );
208  } );
209 
210  // if keyFrame not already here
211  if ( it == m_keyFrames.end() ) {
212  updateCursorSpin();
213 
214  m_keyFrames.push_back( time );
215  std::sort( m_keyFrames.begin(), m_keyFrames.end() );
216 
217  if ( internal ) { emit keyFrameAdded( time ); }
218 
219  updateNbKeyFrameSpin();
220 
221  update();
222  }
223  // KeyFrame already here, change actual KeyFrame
224  else {
225  if ( internal ) { emit keyFrameChanged( std::distance( m_keyFrames.begin(), it ) ); }
226 
227  m_updateKeyFrameFlash = 6;
228  m_keyFrameFlash = time;
229 
230  m_timer->start( 50 );
231 
232  update();
233  }
234 }
235 
236 void 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 );
239  } );
240  if ( it == m_keyFrames.end() ) { return; }
241 
242  if ( internal ) { emit keyFrameDeleted( std::distance( m_keyFrames.begin(), it ) ); }
243 
244  m_keyFrames.erase( it );
245 
246  updateCursorSpin();
247 
248  updateNbKeyFrameSpin();
249 
250  update();
251 }
252 
253 void TimelineFrameSelector::onMoveKeyFrame( Scalar time0, Scalar time1, bool internal ) {
254  if ( Ra::Core::Math::areApproxEqual( time0, time1 ) ) { return; }
255 
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 );
258  } );
259  if ( it == m_keyFrames.end() ) { return; }
260 
261  if ( internal ) { emit keyFrameMoved( std::distance( m_keyFrames.begin(), it ), time1 ); }
262 
263  m_cursor = time1;
264 
265  m_keyFrames.erase( it );
266  m_keyFrames.push_back( m_cursor );
267  std::sort( m_keyFrames.begin(), m_keyFrames.end() );
268 
269  updateCursorSpin();
270 
271  update();
272 }
273 
274 void TimelineFrameSelector::onMoveKeyFrames( Scalar time, Scalar offset, bool internal ) {
275  if ( Ra::Core::Math::areApproxEqual( offset, 0_ra ) ) { return; }
276 
277  auto it = std::find_if( m_keyFrames.begin(), m_keyFrames.end(), [time]( const auto& t ) {
278  return Ra::Core::Math::areApproxEqual( t, time );
279  } );
280  if ( it == m_keyFrames.end() ) { return; }
281 
282  // emit keyFramesMoved before emitting cursorChanged to render the truly Frame on cursor
283  if ( internal ) { emit keyFramesMoved( std::distance( m_keyFrames.begin(), it ), offset ); }
284 
285  std::vector<Scalar> clone;
286  std::transform( m_keyFrames.begin(),
287  m_keyFrames.end(),
288  std::back_inserter( clone ),
289  [time, offset]( Scalar p ) { return ( p < time ? p : p + offset ); } );
290  std::exchange( m_keyFrames, clone );
291 
292  Scalar left = ( offset > 0 ) ? ( time ) : ( time + offset );
293 
294  if ( m_start >= left ) {
295  m_start = std::max(
296  std::min( m_start + offset, m_timelineUI->m_scrollArea->getMaxDuration() ), 0_ra );
297  updateStartSpin();
298  if ( internal ) { emit startChanged( m_start ); }
299  }
300 
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 ); }
306  }
307 
308  if ( m_end >= left ) {
309  m_end = m_end + offset;
310  updateEndSpin();
311  if ( internal ) { emit endChanged( m_end ); }
312  }
313 
314  if ( m_cursor >= left ) {
315  m_cursor = std::max( m_cursor + offset, 0_ra );
316  updateCursorSpin();
317 
318  if ( internal ) { emit cursorChanged( m_cursor ); }
319  }
320 
321  update();
322 }
323 
324 void TimelineFrameSelector::onClearKeyFrames() {
325  m_keyFrames.clear();
326  updateNbKeyFrameSpin();
327 
328  updateCursorSpin();
329  update();
330 }
331 
332 // -------------------------- INTERNAL SLOTS ----------------------------------
333 void TimelineFrameSelector::onSlideLeftSlider( int deltaX ) {
334  { m_timelineUI->m_leftSlider->setStyleSheet( "background-color: green" ); }
335 
336  Scalar pixPerSec = m_timelineUI->m_scrollArea->getPixPerSec();
337  Scalar newStart = m_start + deltaX / pixPerSec;
338 
339  onChangeStart( newStart );
340 }
341 
342 void 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;
346 
347  onChangeEnd( newEnd );
348 }
349 
350 void TimelineFrameSelector::onSetCursorToStart() {
351  onChangeCursor( m_start );
352 }
353 
354 void TimelineFrameSelector::onSetCursorToEnd() {
355  onChangeCursor( m_end );
356 }
357 
358 void TimelineFrameSelector::onSetCursorToPreviousKeyFrame() {
359  auto it = m_keyFrames.rbegin();
360  while ( it != m_keyFrames.rend() && *it >= m_cursor )
361  it++;
362 
363  if ( it != m_keyFrames.rend() ) { onChangeCursor( *it ); }
364 }
365 
366 void TimelineFrameSelector::onSetCursorToNextKeyFrame() {
367  auto it = m_keyFrames.begin();
368  while ( it != m_keyFrames.end() && *it <= m_cursor )
369  it++;
370 
371  if ( it != m_keyFrames.end() ) { onChangeCursor( *it ); }
372 }
373 
374 void TimelineFrameSelector::paintEvent( QPaintEvent* ) {
375 
376  QPainter painter( this );
377  int h = height();
378  int w = width();
379 
380  redrawPlayZone();
381 
382  // DRAW FRAME SCALE
383  painter.setPen( QPen( Qt::lightGray ) );
384  Scalar frameDuration = 1_ra / TIMELINE_FPS;
385  int hUp = h / 3;
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 );
393  }
394 
395  // DRAW CURSOR
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 );
399 
400  // DRAW KEYFRAMES
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 );
406  }
407 
408  // DRAW TIME SCALE
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 );
414  }
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 );
420  }
421 
422  if ( m_updateKeyFrameFlash > 0 ) {
423 
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 );
428  }
429 
430  if ( --m_updateKeyFrameFlash == 0 ) m_timer->stop();
431  }
432 
433  // DRAW MIDDLE RULER SEPARATOR
434  int gap = 5;
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 );
438 
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 );
442 }
443 
444 void TimelineFrameSelector::mousePressEvent( QMouseEvent* event ) {
445  bool shiftDown = event->modifiers() & Qt::Modifier::SHIFT;
446  bool ctrlDown = event->modifiers() & Qt::Modifier::CTRL;
447  // ---------------------- LEFT CLICK --------------------------------------
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(),
451  0_ra );
452  // move cursor without render
453  if ( ctrlDown ) { onChangeCursor( newCursor, false ); }
454  // delete keyFrames between cursor and newCursor
455  else if ( shiftDown ) { deleteZone( m_cursor, newCursor ); }
456  // move cursor and update renderer
457  else {
458  onChangeCursor( newCursor );
459  m_mouseLeftClicked = true;
460  }
461  }
462  // ------------------ RIGHT CLICK -------------------------------------
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(),
466  0_ra );
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 );
469  } );
470  // if already on keyFrame, move current KeyFrame
471  // ------------------- CURSOR ON KEYFRAME -----------------------
472  if ( it != m_keyFrames.end() ) {
473  Scalar nearest = nearestStep( newFrame );
474  // -------------- SINGLE MOVE -------------------------------------
475  if ( shiftDown ) {
476  // if no keyFrame under mouse, move KeyFrame to newFrame
477  auto it2 = std::find_if(
478  m_keyFrames.begin(), m_keyFrames.end(), [nearest]( const auto& t ) {
479  return Ra::Core::Math::areApproxEqual( t, nearest );
480  } );
481  if ( it2 == m_keyFrames.end() && ( std::abs( m_cursor - nearest ) > 1e-5_ra ) ) {
482  onMoveKeyFrame( m_cursor, nearest );
483  }
484  }
485  // ---------- MULTIPLE MOVE -----------------------------------
486  else {
487  // if not before preceding KeyFrame, remove or insert time
488  auto itLeft = it;
489  --itLeft;
490  Scalar left = ( it == m_keyFrames.begin() ) ? ( 0.0 ) : ( *itLeft );
491  if ( nearest > left ) { onMoveKeyFrames( m_cursor, nearest - m_cursor ); }
492  }
493  }
494  // ---------------- CURSOR NOT ON KEYFRAME --------------------------
495  else {
496  // if shiftdown, slide first right KeyFrame to the left
497  // --------------- MOVE RIGHT KEYFRAME TO THE LEFT -----------------
498  if ( shiftDown ) {
499  auto itRight = std::lower_bound( m_keyFrames.begin(), m_keyFrames.end(), newFrame );
500  // if KeyFrames on the right, remove or insert time
501  if ( itRight != m_keyFrames.end() ) {
502  onMoveKeyFrames( *itRight, newFrame - *itRight );
503  }
504  }
505  // if not shiftdown, slide first left KeyFrame to the right
506  // ---------------- MOVE LEFT KEYFRAME TO THE RIGHT -----------
507  else {
508  auto itLeft =
509  --std::lower_bound( m_keyFrames.begin(), m_keyFrames.end(), newFrame );
510  // if KeyFrames on the left, remove or insert time
511  if ( itLeft != m_keyFrames.end() ) {
512  onMoveKeyFrames( *itLeft, newFrame - *itLeft );
513  }
514  }
515  }
516  }
517  // no catch mouse event
518  else {
519  event->ignore();
520  return;
521  }
522  event->accept();
523 }
524 
525 void 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(),
529  0_ra );
530 
531  onChangeCursor( newCursor );
532  }
533  else { event->ignore(); }
534 }
535 
536 void TimelineFrameSelector::mouseReleaseEvent( QMouseEvent* event ) {
537  if ( event->button() == Qt::LeftButton ) {
538  m_mouseLeftClicked = false;
539  event->accept();
540  }
541  else { event->ignore(); }
542 }
543 
544 void 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 );
547  } );
548  if ( it != m_keyFrames.end() ) {
549  m_timelineUI->m_cursorSpin->setStyleSheet( "background-color: yellow" );
550  m_timelineUI->m_removeKeyFrameButton->setEnabled( true );
551  }
552  else {
553  m_timelineUI->m_cursorSpin->setStyleSheet( "background-color: #5555ff" );
554  m_timelineUI->m_removeKeyFrameButton->setEnabled( false );
555  }
556  m_timelineUI->m_cursorSpin->setValue( double( m_cursor ) );
557 }
558 
559 void TimelineFrameSelector::updateStartSpin() {
560  m_timelineUI->m_startSpin->setValue( double( m_start ) );
561  updateDurationSpin();
562 }
563 
564 void TimelineFrameSelector::updateEndSpin() {
565  m_timelineUI->m_endSpin->setValue( double( m_end ) );
566  updateDurationSpin();
567 }
568 
569 void TimelineFrameSelector::updateDurationSpin() {
570  m_timelineUI->m_durationSpin->setValue(
571  double( m_timelineUI->m_scrollArea->getMaxDuration() ) );
572 }
573 
574 void TimelineFrameSelector::updateNbKeyFrameSpin() {
575  m_timelineUI->m_nbKeyFramesSpin->setText( " " + QString::number( m_keyFrames.size() ) + " " );
576 }
577 
578 Scalar TimelineFrameSelector::nearestStep( Scalar time ) const {
579  Scalar deltaT =
580  TIMELINE_AUTO_SUGGEST_CURSOR_RADIUS / m_timelineUI->m_scrollArea->getPixPerSec();
581 
582  Scalar minDist = Scalar( m_timelineUI->m_durationSpin->maximum() );
583  Scalar dist;
584 
585  Scalar newCursor = time;
586 
587  for ( Scalar keyFrame : m_keyFrames ) {
588  dist = qAbs( keyFrame - time );
589  if ( dist < deltaT && dist < minDist ) {
590  minDist = dist;
591  newCursor = keyFrame;
592  }
593  if ( time < keyFrame ) break;
594  }
595 
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 ) {
601  minDist = dist;
602  newCursor = pos;
603  }
604 
605  pos = i * step + 0.5_ra * step;
606  dist = std::abs( pos - time );
607  if ( dist < deltaT && dist < minDist ) {
608  minDist = dist;
609  newCursor = pos;
610  }
611 
612  if ( time < pos ) break;
613  }
614 
615  return newCursor;
616 }
617 
618 void TimelineFrameSelector::deleteZone( Scalar time, Scalar time2 ) {
619  Scalar left = std::min( time, time2 );
620  Scalar right = std::max( time, time2 );
621 
622  Scalar dist = right - left;
623 
624  // remove KeyFrames
625  auto it = m_keyFrames.begin();
626  bool first = true;
627  while ( it != m_keyFrames.end() ) {
628  Scalar keyFrame = *it;
629 
630  if ( keyFrame >= left ) {
631  it = m_keyFrames.erase( it );
632 
633  if ( keyFrame > right ) {
634  if ( first ) {
635  emit keyFramesMoved( std::distance( m_keyFrames.begin(), it ), -dist );
636  first = false;
637  }
638  it = m_keyFrames.insert( it, keyFrame - dist );
639  ++it;
640  }
641  else { emit keyFrameDeleted( std::distance( m_keyFrames.begin(), it ) ); }
642  }
643  else { ++it; }
644  }
645  updateNbKeyFrameSpin();
646 
647  // update playzone
648  Scalar newStart = std::max( std::max( std::min( m_start, left ), m_start - dist ), 0_ra );
649  if ( std::abs( newStart - m_start ) > 1e-5_ra ) {
650  m_start = newStart;
651  updateStartSpin();
652 
653  emit startChanged( m_start );
654  }
655 
656  Scalar newEnd = std::max( std::max( std::min( m_end, left ), m_end - dist ), 0_ra );
657  if ( std::abs( newEnd - m_end ) > 1e-5_ra ) {
658  m_end = newEnd;
659  updateEndSpin();
660 
661  emit endChanged( m_end );
662  }
663 
664  emit cursorChanged( m_cursor );
665 
666  update();
667 }
668 
669 void 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() ) );
676 }
677 
678 } // namespace Ra::Gui
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.
Definition: Math.hpp:42