Skip to content

Commit 02ae19e

Browse files
author
hoshiryu
committed
Cleanup Timeline UI and link to keyframes ;
1 parent 9ca6a78 commit 02ae19e

37 files changed

Lines changed: 3746 additions & 2804 deletions

cmake/filelistGuiBase.cmake

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
set( guibase_sources
22
BaseApplication.cpp
33
SelectionManager/SelectionManager.cpp
4-
Timeline/qdoublespinboxsmart.cpp
5-
Timeline/qframebuttons.cpp
6-
Timeline/qframeselector.cpp
7-
Timeline/qframetimescale.cpp
8-
Timeline/qlabelslider.cpp
9-
Timeline/qscrollarearuler.cpp
10-
Timeline/qspinboxsmart.cpp
11-
Timeline/qtoolbuttonplaypause.cpp
12-
Timeline/qwidgetruler.cpp
13-
Timeline/Session.cpp
4+
Timeline/HelpDialog.cpp
5+
Timeline/TimelineFrameSelector.cpp
6+
Timeline/TimelineTimeScale.cpp
7+
Timeline/TimelineSlider.cpp
8+
Timeline/TimelineScrollArea.cpp
149
Timeline/Timeline.cpp
1510
TimerData/FrameTimerData.cpp
1611
TransformEditor/TransformEditor.cpp
@@ -36,17 +31,12 @@ set( guibase_headers
3631
MainWindowInterface.hpp
3732
RaGuiBase.hpp
3833
SelectionManager/SelectionManager.hpp
34+
Timeline/HelpDialog.hpp
3935
Timeline/Configurations.h
40-
Timeline/qdoublespinboxsmart.h
41-
Timeline/qframebuttons.h
42-
Timeline/qframeselector.h
43-
Timeline/qframetimescale.h
44-
Timeline/qlabelslider.h
45-
Timeline/qscrollarearuler.h
46-
Timeline/qspinboxsmart.h
47-
Timeline/qtoolbuttonplaypause.h
48-
Timeline/qwidgetruler.h
49-
Timeline/Session.h
36+
Timeline/TimelineFrameSelector.h
37+
Timeline/TimelineTimeScale.h
38+
Timeline/TimelineSlider.h
39+
Timeline/TimelineScrollArea.h
5040
Timeline/Timeline.h
5141
TimerData/FrameTimerData.hpp
5242
TransformEditor/TransformEditor.hpp
@@ -73,6 +63,7 @@ set( guibase_inlines
7363
)
7464

7565
set( guibase_uis
66+
Timeline/HelpDialog.ui
7667
Timeline/Timeline.ui
7768
)
7869

doc/timeline.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Timeline And Keyframes
2+
3+
The `Ra::GuiBase::Timeline` class provides display and management of `Ra::Core::Animation::KeyFramedValue`s.
4+
5+
`Ra::Core::Animation::KeyFramedValue`s must be registered into the `Ra::GuiBase::Timeline`, which can be done using the
6+
`Ra::GuiBase::Timeline::registerKeyFramedValue` methods.
7+
8+
We here consider two types of `Ra::Core::Animation::KeyFramedValue`:
9+
10+
## Static KeyFramedValue:
11+
Those are `Ra::Core::Animation::KeyFramedValue`s that are an explicit part of a `Ra::Engine::Entity`, `Ra::Engine::Component` or
12+
`Ra::Engine::RenderObject` data, either filled upon construction or through the `Ra::GuiBase::Timeline`.
13+
Static `Ra::Core::Animation::KeyFramedValue`s must be registered in the `Ra::GuiBase::Timeline` after the object's
14+
construction.
15+
They usually are not bound to an *UpdateCallback* function since the object they
16+
belong to usually calls `Ra::Core::Animation::KeyFramedValue::at` to query the current value.
17+
18+
Example Usage:
19+
```c++
20+
using KeyFramedValue = Ra::Core::Animation::KeyFramedValue<Scalar>;
21+
22+
struct MyComponentWithKeyFrame : public Ra::Engine::Component
23+
{
24+
MyComponentWithKeyFrame( const std::string& name, Ra::Engine::Entity* entity, Scalar value = 0_ra )
25+
: Ra::Engine::Component( name, entity )
26+
, m_keyframes( value, 0_ra )
27+
, m_currentValue( value )
28+
{
29+
// use the linear interpolator instead of the default one
30+
m_keyframes.setInterpolator( Ra::Core::Animation::interpolate<Scalar> );
31+
}
32+
33+
void initialize() override {}
34+
void goTo( Scalar t ) {
35+
// Here we use the value at time t.
36+
m_currentValue = m_keyframes.at( t );
37+
}
38+
void update() {
39+
// Here we can update other data that depend on the value.
40+
// Note: update is called after goTo, so we can use what was stored in m_currentValue.
41+
}
42+
43+
KeyFramedValue m_keyframes;
44+
Scalar m_currentValue;
45+
};
46+
47+
struct MyTimeDependantSystem : public Ra::Engine::AbstractTimedSystem
48+
{
49+
MyTimeDependantSystem( Ra::GuiBase::Timeline* timeline )
50+
: Ra::Engine::AbstractTimedSystem()
51+
, m_timeline( timeline )
52+
{}
53+
54+
void generateTasks( Ra::Core::TaskQueue* taskQueue, const Ra::Engine::FrameInfo& ) override {
55+
// Update all Components data.
56+
for ( auto compEntry : m_components )
57+
{
58+
auto comp = static_cast<MyComponentWithKeyFrame*>( compEntry.second );
59+
auto func = std::bind( &MyComponentWithKeyFrame::update, comp );
60+
auto task = new Ra::Core::FunctionTask( func, "MyUpdateTask" );
61+
taskQueue->registerTask( task );
62+
}
63+
}
64+
void AnimationSystem::handleAssetLoading( Ra::Engine::Entity* entity, const Ra::Core::Asset::FileData* fileData ) {
65+
// Add a Component whenever a file is loaded
66+
auto comp = new MyComponentWithKeyFrame( "MyComponentWithKeyFrame_", entity );
67+
registerComponent( entity, comp );
68+
if ( m_timeline )
69+
{
70+
// on insertion we may want to get the frame value from elsewhere
71+
auto inserter = [comp]( const Scalar& t ) {
72+
// for example value(t) + 1
73+
comp->m_keyframes.insertKeyFrame( t, comp->m_keyframes.at( t ) + 1_ra );
74+
};
75+
// No need for an update callback since the component owns the KeyFramedValue.
76+
m_timeline->registerKeyFramedValue( comp, "KeyFrame", &comp->m_keyframes, inserter );
77+
}
78+
}
79+
void goTo( Scalar t ) override {
80+
// Update all Components' KeyFramedValue.
81+
for ( auto compEntry : m_components )
82+
{
83+
compEntry.second->goTo( t );
84+
}
85+
}
86+
void cacheFrame( const std::string& dir, uint frameID ) const override {}
87+
bool restoreFrame( const std::string& dir, uint frameID ) override {}
88+
89+
Ra::GuiBase::Timeline* m_timeline{nullptr};
90+
};
91+
```
92+
93+
## Dynamic KeyFramedValues:
94+
Those are `Ra::Core::Animation::KeyFramedValue`s that are not part of an `Ra::Engine::Entity`, `Ra::Engine::Component` or
95+
`Ra::Engine::RenderObject` data, but are used to keyframe some of its data.
96+
Dynamic `Ra::Core::Animation::KeyFramedValue`s must be created from and owned by the UI, and registered in the
97+
`Ra::GuiBase::Timeline`.
98+
They are usually bound to an UpdateCallback function since they have to update the
99+
object's data they are linked to.
100+
101+
Example Usage:
102+
```c++
103+
/// Let's say there is a Component class defined as:
104+
struct MyComponent : public Component {
105+
MyComponent( const std::string& name, Ra::Engine::Entity* entity, Scalar value = 0_ra )
106+
: Ra::Engine::Component( name, entity )
107+
, m_currentValue( value )
108+
{}
109+
110+
void initialize() override {}
111+
void update() { std::cout << m_currentValue << std::endl; }
112+
113+
Scalar m_currentValue;
114+
};
115+
116+
/// We here create a plugin that will keyframe the MyComponent::m_currentValue
117+
/// attribute of selected MyComponents in the scene:
118+
119+
// Let's start with some simple UI:
120+
struct MyWidget : public QWidget {
121+
Q_OBJECT
122+
123+
MyWidget( QWigdet* parent = nulltpr ) : QWidget( parent ) {
124+
m_keyFrameValueCheckbox = new QCheckBox( "Keyframe current Value", this );
125+
}
126+
127+
QCheckBox* m_keyFrameValueCheckbox;
128+
};
129+
130+
// Then the plugin:
131+
struct MyPlugin : public QObject, Ra::Plugins::RadiumPluginInterface {
132+
Q_OBJECT
133+
Q_RADIUM_PLUGIN_METADATA
134+
Q_INTERFACES( Ra::Plugins::RadiumPluginInterface )
135+
136+
MyPlugin() = default;
137+
138+
void registerPlugin( const Ra::Plugins::Context& context ) override {
139+
// get timeline
140+
m_timeline = context.m_timeline;
141+
// connect to selection manager
142+
m_selectionManager = context.m_selectionManager;
143+
if ( m_selectionManager )
144+
{
145+
connect( m_selectionManager, &Ra::GuiBase::SelectionManager::currentChanged, this, &MyPlugin::onCurrentChanged );
146+
}
147+
}
148+
bool doAddWidget( QString& name ) override {
149+
return true;
150+
}
151+
QWidget* getWidget() override {
152+
m_widget = new MyWidget;
153+
connect( m_widget->m_keyFrameValueCheckbox, &QCheckBox::toggled, this, &MyPlugin::keyFrameValue );
154+
return m_widget;
155+
}
156+
bool doAddMenu() override { return false; }
157+
QMenu* getMenu() override { return nullptr; }
158+
bool doAddAction( int& nb ) override { return false; }
159+
QAction* getAction( int id ) override { return nullptr; }
160+
void onCurrentChanged( const QModelIndex& current, const QModelIndex& prev ) {
161+
// deal with selection
162+
if ( m_selectionManager->hasSelection() )
163+
{
164+
const Ra::Engine::ItemEntry& ent = m_selectionManager->currentItem();
165+
m_current = dynamic_cast<MyComponent*>( ent.m_component );
166+
}
167+
else
168+
{
169+
m_current = nullptr;
170+
}
171+
m_widget->m_keyFrameValueCheckbox->setChecked( m_keyframes.find( m_current ) != m_keyframes.end() );
172+
}
173+
void keyFrameValue( bool on ) {
174+
// deal with keyframing
175+
if ( m_current == nullptr ) { return; }
176+
if ( on )
177+
{
178+
// keyframe m_current->m_currentValue
179+
auto keyframes = new KeyFramedValue( m_current->m_currentValue, 0, interpolate );
180+
// on insertion we may want to get the frame value from elsewhere
181+
auto inserter = [keyframes]( const Scalar& t ) {
182+
// for example value(t) + 1
183+
keyframes->insertKeyFrame( t, keyframes->at( t ) + 1_ra );
184+
};
185+
// on update we here want to modify the value since the MyComponent cannot do it by itself
186+
Scalar& value = &m_current->m_currentValue;
187+
auto updater = [keyframes, &value]( const Scalar& t ) {
188+
value = keyframes->at( t );
189+
};
190+
// register the new KeyFramedValue
191+
m_keyframes.push_back( keyframes );
192+
m_timeline->registerKeyFramedValue( m_current, "KeyFrame", keyframes, inserter, updater );
193+
}
194+
else
195+
{
196+
// release m_current->m_currentValue from the keyframe
197+
m_timeline->unregisterKeyFramedValue( m_current, "KeyFrame" );
198+
}
199+
};
200+
201+
MyWidget* m_widget{nullptr};
202+
Ra::GuiBase::Timeline* m_timeline{nullptr};
203+
MyComponent* m_current;
204+
std::map<MyComponent*,KeyFramedValue*> m_keyframes;
205+
};
206+
```

src/GuiBase/Timeline/Configurations.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
// ^ ^ <--
3939
// | |
4040
// cursor cursor
41-
// on ruler clicking, automove to nearest keyPose or ruler scale
41+
// on ruler clicking, automove to nearest keyFrame or ruler scale
4242
// if the distance with cursor is below this constant
4343
#define TIMELINE_AUTO_SUGGEST_CURSOR_RADIUS 4 // unit : pixel
4444

@@ -50,11 +50,12 @@
5050

5151
// todo : maybe set global static var
5252
// frame per second to draw position of each frame in ruler
53-
#define TIMELINE_FPS 24
53+
#define TIMELINE_FPS 60
5454

55-
#define TIMELINE_BUFFER_SESSION_MAX_SIZE \
56-
500000000 // 500M bytes in RAM, max bytes for saving user anim for undo/redo stack
55+
// 500M bytes in RAM, max bytes for saving user anim for undo/redo stack
56+
#define TIMELINE_BUFFER_SESSION_MAX_SIZE 500000000
5757

58-
#define TIMELINE_DELAY_AUTO_SAVE 100 // millisecond, auto save environment after delay
58+
// millisecond, auto save environment after delay
59+
#define TIMELINE_DELAY_AUTO_SAVE 100
5960

6061
#endif // RADIUMENGINE_TIMELINE_CONFIG_HPP_
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "ui_HelpDialog.h"
2+
#include <GuiBase/Timeline/HelpDialog.hpp>
3+
4+
namespace Ra::GuiBase {
5+
6+
HelpDialog::HelpDialog( QWidget* parent ) : QDialog( parent ), ui( new Ui::HelpDialog ) {
7+
ui->setupUi( this );
8+
}
9+
10+
HelpDialog::~HelpDialog() {
11+
delete ui;
12+
}
13+
14+
void HelpDialog::reject() {
15+
emit closed();
16+
QDialog::reject();
17+
}
18+
19+
} // namespace Ra::GuiBase
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef RADIUMENGINE_TIMELINE_HELP_HPP__
2+
#define RADIUMENGINE_TIMELINE_HELP_HPP__
3+
4+
#include <QDialog>
5+
6+
namespace Ui {
7+
class HelpDialog;
8+
} // namespace Ui
9+
10+
namespace Ra::GuiBase {
11+
12+
/// Dialog to display navigation/interaction controls.
13+
class HelpDialog : public QDialog
14+
{
15+
Q_OBJECT
16+
17+
public:
18+
explicit HelpDialog( QWidget* parent = nullptr );
19+
~HelpDialog();
20+
21+
signals:
22+
void closed();
23+
24+
public slots:
25+
void reject() override;
26+
27+
private:
28+
Ui::HelpDialog* ui;
29+
};
30+
31+
} // namespace Ra::GuiBase
32+
33+
#endif // RADIUMENGINE_TIMELINE_HELP_HPP__

0 commit comments

Comments
 (0)