Radium Engine  1.5.20
Loading...
Searching...
No Matches
Timeline.cpp
1#include "ui_Timeline.h"
2#include <Gui/Timeline/Timeline.hpp>
3
4#include <QEvent>
5#include <QMessageBox>
6#include <QPainter>
7#include <QTimer>
8#include <QWheelEvent>
9
10#include <Core/Animation/KeyFramedValueInterpolators.hpp>
11#include <Core/Utils/Log.hpp>
12
13#include <Engine/RadiumEngine.hpp>
14#include <Engine/Rendering/RenderObject.hpp>
15#include <Engine/Rendering/RenderObjectManager.hpp>
16#include <Engine/Scene/Component.hpp>
17#include <Engine/Scene/Entity.hpp>
18#include <Engine/Scene/SignalManager.hpp>
19
20#include <Gui/Timeline/HelpDialog.hpp>
21
22using namespace Ra::Core::Utils;
23
24namespace Ra::Gui {
25
26Timeline::Timeline( QWidget* parent ) : QDialog( parent ), ui( new Ui::Timeline ) {
27 ui->setupUi( this );
28
29 // first draw of ruler with current width (default in Timeline.ui) of dialog
30 ui->m_scrollArea->onDrawRuler( width() - 2 ); // left/right border width = 2 *1 pixel
31
32 // --- SET INTERNAL REFERENCES ---
33 ui->frame_timescale->setScrollArea( ui->m_scrollArea );
34 ui->frame_selector->setTimelineUi( ui );
35
36 // --- CREATE INTERNAL CONNECTIONS ---
37 connect( ui->frame_selector,
38 &TimelineFrameSelector::cursorChanged,
39 this,
40 &Timeline::updateKeyFrames );
41 connect( ui->frame_selector,
42 &TimelineFrameSelector::keyFrameAdded,
43 this,
44 &Timeline::onAddingKeyFrame );
45 connect( ui->frame_selector,
46 &TimelineFrameSelector::keyFrameDeleted,
47 this,
48 &Timeline::onRemovingKeyFrame );
49 connect( ui->frame_selector,
50 &TimelineFrameSelector::keyFrameChanged,
51 this,
52 &Timeline::onChangingKeyFrame );
53 connect( ui->frame_selector,
54 &TimelineFrameSelector::keyFrameMoved,
55 this,
56 &Timeline::onMovingKeyFrame );
57 connect( ui->frame_selector,
58 &TimelineFrameSelector::keyFramesMoved,
59 this,
60 &Timeline::onMovingKeyFrames );
61
62 // --- CONNECT INTERNAL SIGNALS TO EXTERNAL SIGNALS ---
63 connect( ui->toolButton_playPause, &QToolButton::toggled, this, &Timeline::playClicked );
64 connect(
65 ui->frame_selector, &TimelineFrameSelector::cursorChanged, this, &Timeline::cursorChanged );
66 connect(
67 ui->frame_selector, &TimelineFrameSelector::startChanged, this, &Timeline::startChanged );
68 connect( ui->frame_selector, &TimelineFrameSelector::endChanged, this, &Timeline::endChanged );
69 connect( ui->frame_selector,
70 &TimelineFrameSelector::durationChanged,
71 this,
72 &Timeline::durationChanged );
73
74 // --- DEAL WITH OBJET REMOVAL ---
75 auto signalManager = Ra::Engine::RadiumEngine::getInstance()->getSignalManager();
76
77 auto& entityAddedObs = signalManager->getEntityCreatedNotifier();
78 m_entityAddObserverId =
79 entityAddedObs.attach( [this]( const Ra::Engine::Scene::ItemEntry& entry ) {
80 auto it = std::find_if(
81 m_entityKeyFrames.begin(), m_entityKeyFrames.end(), [entry]( const auto& frames ) {
82 return entry.m_entity == frames.first;
83 } );
84 if ( it != m_entityKeyFrames.end() ) { m_entityKeyFrames.erase( it ); }
85 } );
86
87 auto& entityRemovedObs = signalManager->getEntityDestroyedNotifier();
88 m_entityRemoveObserverId =
89 entityRemovedObs.attach( [this]( const Ra::Engine::Scene::ItemEntry& entry ) {
90 auto it = std::find_if(
91 m_componentKeyFrames.begin(),
92 m_componentKeyFrames.end(),
93 [entry]( const auto& frames ) { return entry.m_component == frames.first; } );
94 if ( it != m_componentKeyFrames.end() ) { m_componentKeyFrames.erase( it ); }
95 } );
96
97 auto& roAddedObs = signalManager->getRenderObjectCreatedNotifier();
98 m_roAddObserverId = roAddedObs.attach( [this]( const Ra::Engine::Scene::ItemEntry& entry ) {
99 auto it = std::find_if(
100 m_renderObjectKeyFrames.begin(),
101 m_renderObjectKeyFrames.end(),
102 [entry]( const auto& frames ) { return entry.m_roIndex == frames.first; } );
103 if ( it != m_renderObjectKeyFrames.end() ) { m_renderObjectKeyFrames.erase( it ); }
104 } );
105}
106
107void Timeline::detachFromEngine() {
108 // Lifetime of Timeline (as a gui object) is hard to predict. Check all pointer and properties.
109 if ( auto engine = Ra::Engine::RadiumEngine::getInstance() ) {
110 if ( auto signalManager = engine->getSignalManager() ) {
111 if ( m_entityAddObserverId != -1 ) {
112 auto& obs = signalManager->getEntityCreatedNotifier();
113 obs.detach( m_entityAddObserverId );
114 m_entityAddObserverId = -1;
115 }
116 if ( m_entityRemoveObserverId != -1 ) {
117 auto& obs = signalManager->getEntityDestroyedNotifier();
118 obs.detach( m_entityRemoveObserverId );
119 m_entityRemoveObserverId = -1;
120 }
121 if ( m_roAddObserverId ) {
122 auto& obs = signalManager->getRenderObjectCreatedNotifier();
123 obs.detach( m_roAddObserverId );
124 m_roAddObserverId = -1;
125 }
126 }
127 }
128}
129
130Timeline::~Timeline() {
131 detachFromEngine();
132 delete ui;
133}
134
135void Timeline::selectionChanged( const Ra::Engine::Scene::ItemEntry& ent ) {
136 // enables ui if any keyframe
137 auto enableUI = [this]( bool enable ) {
138 ui->comboBox_attribute->setEnabled( enable );
139 ui->toolButton_keyFrame->setEnabled( enable );
140 ui->m_nbKeyFramesSpin->setEnabled( enable );
141 ui->m_removeKeyFrameButton->setEnabled( enable );
142 ui->pushButton_editAttribute->setEnabled( enable );
143 };
144 // reset ui
145 ui->comboBox_attribute->blockSignals( true );
146 ui->comboBox_attribute->clear();
147 ui->comboBox_attribute->blockSignals( false );
148 enableUI( false );
149 onClearKeyFrames();
151 // collects keyframedvalue name; if first one then display times and register as current
152 auto registerFrames = [this]( const Ra::Core::Animation::KeyFramedValueController& frame,
153 const std::string& prefix ) {
154 ui->comboBox_attribute->addItem( QString( ( prefix + frame.m_name ).c_str() ) );
155 if ( ui->comboBox_attribute->count() == 1 ) {
156 const auto times = frame.m_value->getTimes();
157 for ( const auto& t : times ) {
158 ui->frame_selector->onAddingKeyFrame( t, false );
159 }
160 m_current = frame;
161 }
162 };
163 // checks if the given keyframedvalue map contains the given key.
164 // if so, registers all keyframedvalues with the given prefix.
165#define REGISTER_KEYFRAMED_VALUES( map, key, prefix ) \
166 { \
167 auto it = map.find( key ); \
168 if ( it != map.end() ) { \
169 for ( const auto& keyFramedValue : it->second ) { \
170 registerFrames( keyFramedValue, prefix ); \
171 } \
172 } \
173 }
174 // register keyframes for the Entity
175 if ( ent.m_entity == nullptr ) { return; }
176 const std::string& entityName = ent.m_entity->getName();
177 REGISTER_KEYFRAMED_VALUES( m_entityKeyFrames, ent.m_entity, entityName + "::" );
178 // register keyframes for the Component
179 if ( ent.m_component == nullptr ) {
180 enableUI( ui->comboBox_attribute->count() > 0 );
181 return;
182 }
183 const std::string& compName = ent.m_component->getName();
184 REGISTER_KEYFRAMED_VALUES(
185 m_componentKeyFrames, ent.m_component, entityName + "::" + compName + "::" );
186 // register keyframes for the RenderObject
187 if ( ent.m_roIndex == Ra::Core::Utils::Index::Invalid() ) {
188 enableUI( ui->comboBox_attribute->count() > 0 );
189 return;
190 }
191 const auto& roMngr = Ra::Engine::RadiumEngine::getInstance()->getRenderObjectManager();
192 const std::string roName =
193 roMngr->getRenderObject( ent.m_roIndex )->getName() + "_" + std::to_string( ent.m_roIndex );
194 REGISTER_KEYFRAMED_VALUES( m_renderObjectKeyFrames,
195 ent.m_roIndex,
196 entityName + "::" + compName + "::" + roName + "::" );
197#undef REGISTER_KEYFRAMED_VALUES
198 if ( ui->comboBox_attribute->count() > 0 ) {
199 enableUI( true );
200 ui->comboBox_attribute->setCurrentIndex( 0 );
201 }
202}
203
204void Timeline::registerKeyFramedValue(
206 const Ra::Core::Animation::KeyFramedValueController& keyFramedValueController ) {
207 auto& values = m_entityKeyFrames[ent];
208 auto& name = keyFramedValueController.m_name;
209 auto it = std::find_if( values.begin(), values.end(), [&name]( const auto& frame ) {
210 return frame.m_name == name;
211 } );
212 if ( it != values.end() ) {
213 LOG( logWARNING ) << "[Timeline] Already existing KeyFramedValue: " << name
214 << "\" on Entity \"" << ent->getName() << "\". Will not register."
215 << std::endl;
216 return;
217 }
218 values.push_back( keyFramedValueController );
219 selectionChanged( Engine::Scene::ItemEntry( ent ) );
220}
221
222void Timeline::unregisterKeyFramedValue( Ra::Engine::Scene::Entity* ent, const std::string& name ) {
223 auto& values = m_entityKeyFrames[ent];
224 auto it = std::find_if( values.begin(), values.end(), [&name]( const auto& frame ) {
225 return frame.m_name == name;
226 } );
227 if ( it != values.end() ) { values.erase( it ); }
228}
229
230void Timeline::registerKeyFramedValue(
232 const Ra::Core::Animation::KeyFramedValueController& keyFramedValueController ) {
233 auto& values = m_componentKeyFrames[comp];
234 auto name = keyFramedValueController.m_name;
235 auto it = std::find_if( values.begin(), values.end(), [name]( const auto& frame ) {
236 return frame.m_name == name;
237 } );
238 if ( it != values.end() ) {
239 LOG( logWARNING ) << "[Timeline] Already existing KeyFramedValue: \"" << name
240 << "\" on Component \"" << comp->getName() << "\". Will not register."
241 << std::endl;
242 return;
243 }
244 values.push_back( keyFramedValueController );
245 selectionChanged( Engine::Scene::ItemEntry( comp->getEntity(), comp ) );
246}
247
248void Timeline::unregisterKeyFramedValue( Ra::Engine::Scene::Component* comp,
249 const std::string& name ) {
250 auto& values = m_componentKeyFrames[comp];
251 auto it = std::find_if( values.begin(), values.end(), [&name]( const auto& frame ) {
252 return frame.m_name == name;
253 } );
254 if ( it != values.end() ) { values.erase( it ); }
255}
256
257void Timeline::registerKeyFramedValue(
258 Ra::Core::Utils::Index roIdx,
259 const Ra::Core::Animation::KeyFramedValueController& keyFramedValueController ) {
260 auto& values = m_renderObjectKeyFrames[roIdx];
261 auto name = keyFramedValueController.m_name;
262 auto it = std::find_if( values.begin(), values.end(), [&name]( const auto& frame ) {
263 return frame.m_name == name;
264 } );
265 if ( it != values.end() ) {
266 LOG( logWARNING ) << "[Timeline] Already existing KeyFramedValue: " << name
267 << "\" on RenderObject \"" << roIdx << "\". Will not register."
268 << std::endl;
269 return;
270 }
271 values.push_back( keyFramedValueController );
272 auto roMgr = Engine::RadiumEngine::getInstance()->getRenderObjectManager();
273 auto RO = roMgr->getRenderObject( roIdx );
274 auto comp = RO->getComponent();
275 selectionChanged( Engine::Scene::ItemEntry( comp->getEntity(), comp, roIdx ) );
276}
277
278void Timeline::unregisterKeyFramedValue( Ra::Core::Utils::Index roIdx, const std::string& name ) {
279 auto& values = m_renderObjectKeyFrames[roIdx];
280 auto it = std::find_if( values.begin(), values.end(), [&name]( const auto& frame ) {
281 return frame.m_name == name;
282 } );
283 if ( it != values.end() ) { values.erase( it ); }
284}
285
286Scalar Timeline::getTime() const {
287 return Scalar( ui->m_cursorSpin->value() );
288}
289
290void Timeline::onChangeStart( Scalar time ) {
291 ui->frame_selector->onChangeStart( time, false );
292}
293
294void Timeline::onChangeEnd( Scalar time ) {
295 ui->frame_selector->onChangeEnd( time, false );
296}
297
298void Timeline::onChangeDuration( Scalar time ) {
299 ui->frame_selector->onChangeDuration( time, false );
300}
301
302void Timeline::onChangeCursor( Scalar time ) {
303 if ( Ra::Core::Math::areApproxEqual( time, Scalar( ui->m_cursorSpin->value() ) ) ) { return; }
304 ui->frame_selector->onChangeCursor( time, false );
305 updateKeyFrames( time );
306}
307
308void Timeline::onSetPlay( bool play ) {
309 ui->toolButton_playPause->setChecked( play );
310}
311
312// todo : ctrlDown = shiftDown = false, on focus event (initialize state)
313void Timeline::resizeEvent( QResizeEvent* event ) {
314 ui->m_scrollArea->onDrawRuler( event->size().width() - 2 );
315}
316
317void Timeline::on_pingPong_toggled( bool checked ) {
318 emit setPingPong( checked );
319}
320
321void Timeline::updateKeyFrames( Scalar time ) {
322 auto update = [time]( auto& keyFrames ) {
323 for ( auto& KF : keyFrames ) {
324 for ( auto& kf : KF.second ) {
325 kf.updateKeyFrame( time );
326 }
327 }
328 };
329 update( m_entityKeyFrames );
330 update( m_componentKeyFrames );
331 update( m_renderObjectKeyFrames );
332}
333
334void Timeline::onClearKeyFrames() {
335 ui->frame_selector->onClearKeyFrames();
336}
337
338void Timeline::onAddingKeyFrame( Scalar time ) {
339 if ( m_current.m_value ) {
340 m_current.insertKeyFrame( time );
341 emit keyFrameAdded( time );
342 }
343}
344
345void Timeline::onRemovingKeyFrame( size_t i ) {
346 if ( m_current.m_value ) {
347 const Scalar time = m_current.m_value->getTimes()[i];
348 if ( m_current.m_value->removeKeyFrame( i ) ) {
349 m_current.updateKeyFrame( time );
350 emit keyFrameDeleted( i );
351 }
352 else {
353 LOG( logWARNING ) << "[Timeline] Error: Cannot remove keyFrame at "
354 << Scalar( ui->m_cursorSpin->value() );
355 }
356 }
357}
358
359void Timeline::onChangingKeyFrame( size_t i ) {
360 if ( m_current.m_value ) {
361 Scalar time = m_current.m_value->getTimes()[i];
362 m_current.insertKeyFrame( time );
363 emit keyFrameChanged( i );
364 }
365}
366
367void Timeline::onMovingKeyFrame( size_t i, Scalar time1 ) {
368 if ( m_current.m_value ) {
369 if ( !Ra::Core::Math::areApproxEqual( m_current.m_value->getTimes()[i], time1 ) ) {
370 m_current.m_value->moveKeyFrame( i, time1 );
371 m_current.updateKeyFrame( time1 );
372 emit keyFrameMoved( i, time1 );
373 }
374 }
375}
376
377void Timeline::onMovingKeyFrames( size_t first, Scalar offset ) {
378 if ( m_current.m_value ) {
379 auto times = m_current.m_value->getTimes();
380 const Scalar time = times[first];
381 if ( offset < 0 ) {
382 for ( int i = 0; i < int( times.size() ); ++i ) {
383 const Scalar t = times[i];
384 if ( t >= time ) { m_current.m_value->moveKeyFrame( i, t + offset ); }
385 }
386 }
387 else // go from the end to ensure we do not mess up keyframes
388 {
389 for ( int i = times.size() - 1; i >= 0; --i ) {
390 const Scalar t = times[i];
391 if ( t >= time ) { m_current.m_value->moveKeyFrame( i, t + offset ); }
392 }
393 }
394 m_current.updateKeyFrame( time );
395 emit keyFramesMoved( first, offset );
396 }
397}
398
399void Timeline::on_comboBox_attribute_currentTextChanged( const QString& arg1 ) {
400 onClearKeyFrames();
402 const QStringList names = arg1.split( "::" );
403
404 // prints a warning message for the attribute and returns
405#define LOG_AND_RETURN \
406 LOG( logWARNING ) << "[Timeline] Error: attribute \"" << arg1.toStdString() \
407 << "\"'s name is not conform."; \
408 return
409
410 // Checks if the given vector of KeyFramedValues contains a KeyFramedValue
411 // with the given name.
412 // If so, set the current frame to this KeyFramedValue;
413 // If not, prints a warning message for the attribute and returns.
414#define GET_KEYFRAMEDVALUE( list, name ) \
415 { \
416 auto it = std::find_if( list.begin(), list.end(), [&name]( const auto& frame ) { \
417 return frame.m_name == name; \
418 } ); \
419 if ( it == list.end() ) { LOG_AND_RETURN; } \
420 m_current = *it; \
421 }
422
423 // Checks if the given map has an element fulfilling lambda.
424 // If so, set list to the associated vector of KeyFramedValues.
425 // If not, prints a warning message for the attribute and returns.
426#define GET_KEYFRAMEDVALUE_LIST( map, lambda ) \
427 { \
428 auto keyFramedValues = std::find_if( map.begin(), map.end(), lambda ); \
429 if ( keyFramedValues == map.end() ) { LOG_AND_RETURN; } \
430 list = keyFramedValues->second; \
431 }
432
433 switch ( names.size() ) {
434 case 2: {
435 const std::string entityName = names.at( 0 ).toStdString();
436 const std::string frameName = names.at( 1 ).toStdString();
437 auto lambda = [entityName]( const auto& frame ) {
438 return frame.first->getName() == entityName;
439 };
440 GET_KEYFRAMEDVALUE_LIST( m_entityKeyFrames, lambda );
441 GET_KEYFRAMEDVALUE( list, frameName );
442 } break;
443 case 3: {
444 const std::string compName = names.at( 1 ).toStdString();
445 const std::string frameName = names.at( 2 ).toStdString();
446 auto lambda = [compName]( const auto& frame ) {
447 return frame.first->getName() == compName;
448 };
449 GET_KEYFRAMEDVALUE_LIST( m_componentKeyFrames, lambda );
450 GET_KEYFRAMEDVALUE( list, frameName );
451 } break;
452 case 4: {
453 const QStringList fullRoName = names.at( 2 ).split( '_' );
454 bool ok;
455 const auto roIdx = fullRoName.last().toInt( &ok );
456 if ( !ok ) { LOG_AND_RETURN; }
457 auto lambda = [&roIdx]( const auto& frame ) { return frame.first == roIdx; };
458 const std::string frameName = names.at( 3 ).toStdString();
459 GET_KEYFRAMEDVALUE_LIST( m_renderObjectKeyFrames, lambda );
460 GET_KEYFRAMEDVALUE( list, frameName );
461 } break;
462 default:
463 LOG_AND_RETURN;
464 }
465#undef GET_KEYFRAMEDVALUE_LIST
466#undef GET_KEYFRAMEDVALUE
467#undef LOG_AND_RETURN
468
469 // update ui
470 const auto times = m_current.m_value->getTimes();
471 for ( const auto& t : times ) {
472 ui->frame_selector->onAddingKeyFrame( t, false );
473 }
474}
475
476void Timeline::on_pushButton_editAttribute_clicked() {
477 // TODO
478}
479
480void Timeline::on_toolButton_help_clicked() {
481 static bool is_showing = false;
482 if ( !is_showing ) {
483 HelpDialog* dialog = new HelpDialog( this );
484 dialog->show();
485 is_showing = true;
486 connect( dialog, &HelpDialog::closed, [=]() { is_showing = false; } );
487 }
488}
489
490} // namespace Ra::Gui
T at(T... args)
virtual std::vector< Scalar > getTimes() const =0
A component is an element that can be updated by a system. It is also linked to some other components...
Definition Component.hpp:31
virtual const std::string & getName() const
Return the component's name.
Definition Component.hpp:56
virtual Entity * getEntity() const
Return the entity the component belongs to.
Definition Component.hpp:53
An entity is an scene element. It ties together components with a transform.
Definition Entity.hpp:23
Dialog to display navigation/interaction controls.
The Timeline class provides display and management of time, as well as keyframes.
Definition Timeline.hpp:38
T endl(T... args)
T find_if(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.
Definition Math.hpp:42
Ra::Core::Utils::Index m_roIndex
RO index of the represented object.
Definition ItemEntry.hpp:67
Entity * m_entity
The entity represented by the item, or owning the object represented.
Definition ItemEntry.hpp:60
T time(T... args)
T to_string(T... args)