Bezier curve animation demo
[platform/core/uifw/dali-demo.git] / examples / bezier-curve / bezier-curve-example.cpp
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 #include <dali/dali.h>
19 #include <dali-toolkit/dali-toolkit.h>
20 #include "shared/view.h"
21
22 #include <sstream>
23
24 using namespace Dali;
25 using namespace Dali::Toolkit;
26
27 namespace
28 {
29
30 const Vector4 GRID_BACKGROUND_COLOR(0.85f, 0.85f, 0.85f, 1.0f);
31 const Vector4 CONTROL_POINT1_COLOR(Color::MAGENTA);
32 const Vector4 CONTROL_POINT2_COLOR(0.0, 0.9, 0.9, 1.0);
33 const Vector3 CONTROL_POINT1_ORIGIN(-100,  200, 0);
34 const Vector3 CONTROL_POINT2_ORIGIN( 100, -200, 0);
35 const char* const CIRCLE1_IMAGE( DEMO_IMAGE_DIR "circle1.png" );
36 const char* const CIRCLE2_IMAGE( DEMO_IMAGE_DIR "circle2.png" );
37 const char* const ANIMATION_BACKGROUND( DEMO_IMAGE_DIR "slider-skin.9.png" );
38 const char* APPLICATION_TITLE("Bezier curve animation");
39 const float ANIM_LEFT_FACTOR(0.2f);
40 const float ANIM_RIGHT_FACTOR(0.8f);
41
42
43 const char* CURVE_VERTEX_SHADER = DALI_COMPOSE_SHADER
44   (
45     attribute mediump vec2 aPosition;
46     uniform mediump mat4 uMvpMatrix;
47     uniform vec3 uSize;
48     void main()
49     {
50       gl_Position = uMvpMatrix * vec4(aPosition*uSize.xy, 0.0, 1.0);
51     }
52    );
53
54 const char* CURVE_FRAGMENT_SHADER = DALI_COMPOSE_SHADER
55   (
56     uniform lowp vec4 uColor;
57     void main()
58     {
59       gl_FragColor = vec4(0.0,0.0,0.0,1.0);
60     }
61    );
62
63
64 inline float Clamp(float v, float min, float max)
65 {
66   if(v<min) return min;
67   if(v>max) return max;
68   return v;
69 }
70
71 struct HandlePositionConstraint
72 {
73   HandlePositionConstraint( float minRelX, float maxRelX, float minRelY, float maxRelY )
74   : minRelX(minRelX), maxRelX(maxRelX), minRelY(minRelY), maxRelY(maxRelY)
75   {
76   }
77
78   void operator()( Vector3& current, const PropertyInputContainer& inputs )
79   {
80     Vector3 size( inputs[0]->GetVector3() );
81     current.x = Clamp(current.x, minRelX*size.x, maxRelX*size.x );
82     current.y = Clamp(current.y, minRelY*size.y, maxRelY*size.y );
83   }
84
85   float minRelX;
86   float maxRelX;
87   float minRelY;
88   float maxRelY;
89 };
90
91 void AnimatingPositionConstraint( Vector3& current, const PropertyInputContainer& inputs )
92 {
93   float positionFactor( inputs[0]->GetFloat() ); // -1 - 2
94   Vector3 size( inputs[1]->GetVector3() );
95
96   current.x = size.x * (positionFactor-0.5f); // size * (-1.5 - 1.5)
97 }
98
99 } //unnamed namespace
100
101
102
103 class BezierCurveExample : public ConnectionTracker
104 {
105 public:
106
107   BezierCurveExample( Application& application )
108   : mApplication( application ),
109     mDuration(2.0f),
110     mGoingRight(true)
111   {
112     // Connect to the Application's Init signal
113     mApplication.InitSignal().Connect( this, &BezierCurveExample::Create );
114   }
115
116   ~BezierCurveExample()
117   {
118     // Nothing to do here;
119   }
120
121   // The Init signal is received once (only) during the Application lifetime
122   void Create( Application& application )
123   {
124     // Hide the indicator bar
125     application.GetWindow().ShowIndicator( Dali::Window::INVISIBLE );
126
127     Stage stage = Stage::GetCurrent();
128     stage.KeyEventSignal().Connect( this, &BezierCurveExample::OnKeyEvent );
129
130     CreateBackground(stage);
131
132     mControlPointScale = 0.5f;
133     mControlPointZoomScale = mControlPointScale * 2.0f;
134
135     mContentLayer = Layer::New();
136     mContentLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
137     mContentLayer.TouchSignal().Connect(this, &BezierCurveExample::OnTouchLayer);
138     mContentLayer.SetParentOrigin( ParentOrigin::CENTER );
139     stage.Add( mContentLayer );
140
141     // 6 rows: title, grid, coords, play, anim1, anim2
142     TableView contentLayout = TableView::New(5, 1);
143     contentLayout.SetName("contentLayout");
144     contentLayout.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
145     contentLayout.SetCellPadding( Size( 30, 30 ) );
146     contentLayout.SetParentOrigin(ParentOrigin::TOP_CENTER);
147     contentLayout.SetAnchorPoint(AnchorPoint::TOP_CENTER);
148     mContentLayer.Add( contentLayout );
149
150     // Create a TextLabel for the application title.
151     Toolkit::TextLabel label = Toolkit::TextLabel::New( APPLICATION_TITLE );
152     label.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" );
153     label.SetProperty( Toolkit::TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER" );
154     label.SetProperty( Toolkit::TextLabel::Property::TEXT_COLOR, Color::BLACK );
155     contentLayout.Add( label );
156     contentLayout.SetFitHeight(0);
157
158     mGrid = Control::New();
159
160     mGrid.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH );
161     mGrid.SetResizePolicy( ResizePolicy::DIMENSION_DEPENDENCY, Dimension::HEIGHT );
162
163     mGrid.SetParentOrigin(ParentOrigin::CENTER);
164     mGrid.SetAnchorPoint(AnchorPoint::CENTER);
165     mGrid.SetBackgroundColor(GRID_BACKGROUND_COLOR);
166
167     contentLayout.Add( mGrid );
168     contentLayout.SetCellAlignment(1, HorizontalAlignment::CENTER, VerticalAlignment::CENTER );
169     CreateCubic(mGrid);
170     CreateControlPoints( mGrid ); // Control points constrained to double height of grid
171
172     mCoefficientLabel = TextLabel::New();
173     mCoefficientLabel.SetProperty( TextLabel::Property::ENABLE_MARKUP, true );
174     mCoefficientLabel.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" );
175     mCoefficientLabel.SetProperty( Toolkit::TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER" );
176     mCoefficientLabel.SetParentOrigin(ParentOrigin::CENTER);
177
178     contentLayout.Add( mCoefficientLabel );
179     SetLabel( Vector2(0,0), Vector2(1,1));
180     contentLayout.SetCellAlignment(2, HorizontalAlignment::CENTER, VerticalAlignment::CENTER );
181     contentLayout.SetFitHeight(2);
182
183     // Setup Play button and 2 icons to show off current anim and linear anim
184
185     PushButton play = PushButton::New();
186     play.SetName("Play");
187     play.SetParentOrigin(ParentOrigin::CENTER);
188     play.SetLabelText("Play");
189     play.ClickedSignal().Connect( this, &BezierCurveExample::OnPlayClicked );
190
191     contentLayout.Add( play );
192     contentLayout.SetCellAlignment(3, HorizontalAlignment::CENTER, VerticalAlignment::CENTER );
193     contentLayout.SetFitHeight(3);
194
195     auto animContainer = Control::New();
196     animContainer.SetName("AnimationContainer");
197     animContainer.SetParentOrigin( ParentOrigin::CENTER );
198     animContainer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
199
200     auto animRail = Control::New();
201     animRail.SetProperty( Control::Property::BACKGROUND, Property::Map()
202                           .Add( Visual::Property::TYPE, Visual::IMAGE )
203                           .Add( ImageVisual::Property::URL, ANIMATION_BACKGROUND ) );
204     animRail.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
205     animRail.SetSizeModeFactor( Vector3( 0.666f, 0.2f, 1.0f ) );
206     animRail.SetParentOrigin( ParentOrigin::CENTER );
207     animContainer.Add( animRail );
208
209     contentLayout.Add( animContainer );
210     contentLayout.SetFixedHeight(4, 150 );
211
212     mAnimIcon1 = ImageView::New( CIRCLE1_IMAGE );
213     mAnimIcon1.SetParentOrigin( ParentOrigin::CENTER );
214     mAnimIcon1.SetAnchorPoint( AnchorPoint::CENTER );
215
216     // Would like some means of setting and animating position as a percentage of
217     // parent size without using constraints, but this will have to suffice for the moment.
218     mPositionFactorIndex = mAnimIcon1.RegisterProperty( "positionFactor", ANIM_LEFT_FACTOR); // range: 0-1 (+/- 1)
219     Constraint constraint = Constraint::New<Vector3>( mAnimIcon1, Actor::Property::POSITION, AnimatingPositionConstraint );
220     constraint.AddSource( Source( mAnimIcon1, mPositionFactorIndex ) );
221     constraint.AddSource( Source( animContainer, Actor::Property::SIZE ) );
222     constraint.Apply();
223
224     animContainer.Add( mAnimIcon1 );
225
226     // First UpdateCurve needs to run after size negotiation and after images have loaded
227     mGrid.OnRelayoutSignal().Connect( this, &BezierCurveExample::InitialUpdateCurve );
228
229     auto controlPoint1 = Control::DownCast( mControlPoint1 );
230     if( controlPoint1 )
231     {
232       controlPoint1.ResourceReadySignal().Connect( this, &BezierCurveExample::ControlPointReady );
233     }
234
235     auto controlPoint2 = Control::DownCast( mControlPoint2 );
236     if( controlPoint2 )
237     {
238       controlPoint2.ResourceReadySignal().Connect( this, &BezierCurveExample::ControlPointReady );
239     }
240   }
241
242   void ControlPointReady( Control control )
243   {
244     UpdateCurve();
245   }
246
247   void InitialUpdateCurve(Actor actor)
248   {
249     UpdateCurve();
250   }
251
252   void CreateBackground( Stage stage )
253   {
254     Toolkit::Control background = Dali::Toolkit::Control::New();
255     background.SetAnchorPoint( Dali::AnchorPoint::CENTER );
256     background.SetParentOrigin( Dali::ParentOrigin::CENTER );
257     background.SetResizePolicy( Dali::ResizePolicy::FILL_TO_PARENT, Dali::Dimension::ALL_DIMENSIONS );
258
259     Property::Map map;
260     map.Insert( Visual::Property::TYPE,  Visual::COLOR );
261     map.Insert( ColorVisual::Property::MIX_COLOR, Vector4( 253/255.0f, 245/255.0f, 230/255.0f, 1.0f ) );
262     background.SetProperty( Dali::Toolkit::Control::Property::BACKGROUND, map );
263     stage.Add( background );
264   }
265
266   void CreateCubic(Actor parent)
267   {
268     // Create a mesh to draw the cubic as a single line
269     mCurve = Actor::New();
270     mCurve.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
271     mCurve.SetParentOrigin( ParentOrigin::CENTER );
272
273     Shader shader = Shader::New( CURVE_VERTEX_SHADER, CURVE_FRAGMENT_SHADER );
274
275     Property::Map curveVertexFormat;
276     curveVertexFormat["aPosition"] = Property::VECTOR2;
277     mCurveVertices = PropertyBuffer::New( curveVertexFormat );
278     Vector2 vertexData[2] = { Vector2(-0.5f, 0.5f), Vector2( 0.5f, -0.5f ) };
279     mCurveVertices.SetData( vertexData, 2 );
280
281     Geometry geometry = Geometry::New();
282     geometry.AddVertexBuffer( mCurveVertices );
283     geometry.SetType( Geometry::LINE_STRIP );
284
285     Renderer renderer = Renderer::New( geometry, shader );
286     mCurve.AddRenderer( renderer );
287     parent.Add(mCurve);
288   }
289
290   Actor CreateControlPoint( Actor parent, const char* url, Vector3 position)
291   {
292     Actor actor = ImageView::New( url );
293     actor.SetScale( mControlPointScale);
294     actor.SetParentOrigin( ParentOrigin::CENTER );
295     // Curve and line drawing works off current value (i.e. last update frame's value). Need to animate to ensure
296     // initial position is baked to both frames before initially drawing the curve.
297     auto positionAnimation = Animation::New( 0.01f );
298     positionAnimation.AnimateTo( Property( actor, Actor::Property::POSITION ), position, AlphaFunction::EASE_IN_OUT );
299     positionAnimation.Play();
300     positionAnimation.FinishedSignal().Connect( this, &BezierCurveExample::OnAnimationFinished );
301
302     // Set up constraints for drag/drop
303     Constraint constraint = Constraint::New<Vector3>( actor, Actor::Property::POSITION, HandlePositionConstraint( -0.5, 0.5, -1, 1));
304     constraint.AddSource( Source( parent, Actor::Property::SIZE ) );
305     constraint.Apply();
306
307     actor.TouchSignal().Connect(this, &BezierCurveExample::OnTouchControlPoint);
308     return actor;
309   }
310
311   Actor CreateControlLine( PropertyBuffer vertexBuffer )
312   {
313     Actor line = Actor::New();
314     line.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
315     line.SetParentOrigin( ParentOrigin::CENTER );
316
317     Shader shader = Shader::New( CURVE_VERTEX_SHADER, CURVE_FRAGMENT_SHADER );
318     Geometry geometry = Geometry::New();
319     geometry.AddVertexBuffer( vertexBuffer );
320     geometry.SetType( Geometry::LINE_STRIP );
321
322     Renderer renderer = Renderer::New( geometry, shader );
323     line.AddRenderer( renderer );
324     return line;
325   }
326
327   void CreateControlPoints( Actor parent )
328   {
329     mControlPoint1 = CreateControlPoint( parent,
330                                          CIRCLE1_IMAGE,
331                                          CONTROL_POINT1_ORIGIN );
332     mControlPoint1Id = mControlPoint1.GetId();
333
334     mControlPoint2 = CreateControlPoint( parent,
335                                          CIRCLE2_IMAGE,
336                                          CONTROL_POINT2_ORIGIN );
337     mControlPoint2Id = mControlPoint2.GetId();
338
339     Property::Map lineVertexFormat;
340     lineVertexFormat["aPosition"] = Property::VECTOR2;
341     mLine1Vertices = PropertyBuffer::New( lineVertexFormat );
342     mLine2Vertices = PropertyBuffer::New( lineVertexFormat );
343
344     mControlLine1 = CreateControlLine( mLine1Vertices );
345     mControlLine2 = CreateControlLine( mLine2Vertices );
346
347     parent.Add( mControlLine1 );
348     parent.Add( mControlLine2 );
349     parent.Add( mControlPoint1 );
350     parent.Add( mControlPoint2 );
351   }
352
353   void SetLabel( Vector2 pos1, Vector2 pos2 )
354   {
355     std::ostringstream oss;
356     oss.setf(std::ios::fixed, std::ios::floatfield);
357     oss.precision(2);
358     oss << "( <color value='#971586'>" << pos1.x << ", " << pos1.y << ", </color>";
359     oss << "<color value='#e7640d'>" << pos2.x << ", " << pos2.y << "</color>";
360     oss << "<color value='black'> )</color>";
361
362     mCoefficientLabel.SetProperty( TextLabel::Property::TEXT, oss.str() );
363   }
364
365   Vector2 AlignToGrid( Vector3 actorPos, Vector3 gridSize )
366   {
367     actorPos /= gridSize; // => -0.5 - 0.5
368     actorPos.x = Clamp( actorPos.x, -0.5f, 0.5f );
369     return Vector2( actorPos.x + 0.5f, 0.5f - actorPos.y );
370   }
371
372   void GetControlPoints(Vector2& pt1, Vector2& pt2)
373   {
374     Vector3 gridSize = mGrid.GetProperty<Vector3>( Actor::Property::SIZE ); // Get target value
375
376     pt1 = AlignToGrid( mControlPoint1.GetCurrentPosition(), gridSize );
377     pt2 = AlignToGrid( mControlPoint2.GetCurrentPosition(), gridSize );
378   }
379
380   /**
381    * @param[in] actor The actor to get the position from
382    * @param[out] point The point in the grid in the range -0.5 -> 0.5 in x and y, with y up.
383    * @param[out] position The actor position, floored to the nearest pixel
384    */
385   void GetPoint( Actor actor, Vector2& point, Vector2& position)
386   {
387     auto gridSize = mGrid.GetProperty<Vector3>( Actor::Property::SIZE ); // Get target value
388     auto currentPosition = actor.GetCurrentPosition(); // Get constrained current value
389
390     position = Vector2( std::floor( currentPosition.x ), std::floor( currentPosition.y ) );
391
392     point.x = Clamp( position.x / gridSize.x, -0.5f, 0.5f ) + 0.5f;
393     point.y = 0.5f - position.y / gridSize.y;
394   }
395
396   void UpdateCurve()
397   {
398     Vector2 point1, point2;
399     Vector2 position1, position2;
400     const int NUMBER_OF_SEGMENTS(40);
401
402     GetPoint( mControlPoint1, point1, position1 );
403     GetPoint( mControlPoint2, point2, position2 );
404
405     if( position1 != mLastControlPointPosition1 ||
406         position2 != mLastControlPointPosition2 )
407     {
408       mLastControlPointPosition1 = position1;
409       mLastControlPointPosition2 = position2;
410
411       SetLabel( point1, point2 );
412
413       Path path = Path::New();
414       path.AddPoint(Vector3::ZERO);
415       path.AddPoint(Vector3(1.0f, 1.0f, 1.0f));
416       path.AddControlPoint( Vector3( point1.x, point1.y, 0 ) );
417       path.AddControlPoint( Vector3( point2.x, point2.y, 0 ) );
418
419       Dali::Vector<float> verts;
420
421       verts.Resize(2*(NUMBER_OF_SEGMENTS+1)); // 1 more point than segment
422       for( int i=0; i<=NUMBER_OF_SEGMENTS; ++i)
423       {
424         Vector3 position, tangent;
425         path.Sample( i/float(NUMBER_OF_SEGMENTS), position, tangent );
426         verts[i*2] = position.x-0.5;
427         verts[i*2+1] = 0.5-position.y;
428       }
429       mCurveVertices.SetData(&verts[0], NUMBER_OF_SEGMENTS+1);
430
431       Vector4 line1( -0.5f, 0.5f, point1.x-0.5f, 0.5f-point1.y );
432       mLine1Vertices.SetData( line1.AsFloat(), 2 );
433
434       Vector4 line2( 0.5f, -0.5f, point2.x-0.5f, 0.5f-point2.y );
435       mLine2Vertices.SetData( line2.AsFloat(), 2 );
436     }
437   }
438
439   bool OnTouchControlPoint( Actor controlPoint, const TouchData& event )
440   {
441     if( event.GetPointCount() > 0 )
442     {
443       if( event.GetState( 0 ) == PointState::DOWN )
444       {
445         Vector2 screenPoint = event.GetScreenPosition( 0 );
446         mRelativeDragPoint = screenPoint;
447         mRelativeDragPoint -= Vector2(controlPoint.GetCurrentPosition());
448         mDragActor = controlPoint;
449         mDragAnimation = Animation::New(0.25f);
450         mDragAnimation.AnimateTo( Property(mDragActor, Actor::Property::SCALE), Vector3( mControlPointZoomScale, mControlPointZoomScale, 1.0f), AlphaFunction::EASE_OUT);
451         mDragAnimation.Play();
452       }
453     }
454     return false; // Don't mark this as consumed - let the layer get the touch
455   }
456
457   bool OnTouchLayer( Actor actor, const TouchData& event )
458   {
459     if( event.GetPointCount() > 0 )
460     {
461       if( mDragActor )
462       {
463         Vector3 position( event.GetScreenPosition( 0 ) );
464
465         mDragActor.SetPosition( position - Vector3( mRelativeDragPoint ) );
466
467         if( event.GetState( 0 ) == PointState::UP ) // Stop dragging
468         {
469           mDragAnimation = Animation::New(0.25f);
470           mDragAnimation.AnimateTo( Property( mDragActor, Actor::Property::SCALE ), Vector3( mControlPointScale, mControlPointScale, 1.0f), AlphaFunction::EASE_IN);
471           mDragAnimation.FinishedSignal().Connect( this, &BezierCurveExample::OnAnimationFinished );
472           mDragAnimation.Play();
473           mDragActor.Reset();
474         }
475       }
476       UpdateCurve();
477     }
478     return false;
479   }
480
481   void OnAnimationFinished( Animation& animation )
482   {
483     UpdateCurve();
484   }
485
486   bool OnPlayClicked( Button button )
487   {
488     if( ! mBezierAnimation )
489     {
490       mBezierAnimation = Animation::New( mDuration );
491     }
492     mBezierAnimation.Stop();
493     mBezierAnimation.Clear();
494
495     float positionFactor = ANIM_LEFT_FACTOR;
496     if( mGoingRight )
497     {
498       positionFactor = ANIM_RIGHT_FACTOR;
499       mGoingRight = false;
500     }
501     else
502     {
503       mGoingRight = true;
504     }
505
506     Vector2 pt1, pt2;
507     GetControlPoints(pt1, pt2);
508
509     mBezierAnimation.AnimateTo( Property(mAnimIcon1, mPositionFactorIndex), positionFactor, AlphaFunction( pt1, pt2 ) );
510     mBezierAnimation.Play();
511     return true;
512   }
513
514   /**
515    * Main key event handler
516    */
517   void OnKeyEvent(const KeyEvent& event)
518   {
519
520     if( event.state == KeyEvent::Down && (IsKey( event, DALI_KEY_ESCAPE) || IsKey( event, DALI_KEY_BACK ))  )
521     {
522       mApplication.Quit();
523     }
524   }
525
526 private:
527   Application& mApplication;
528   Actor mControlPoint1;
529   Actor mControlPoint2;
530   Actor mControlLine1;
531   Actor mControlLine2;
532   ImageView mAnimIcon1;
533   ImageView mAnimIcon2;
534   Actor mDragActor;
535   Actor mCurve;
536   TextLabel mCoefficientLabel;
537   Layer mContentLayer;
538   Control mGrid;
539   Timer mTimer;
540   Animation mDragAnimation;
541   Animation mBezierAnimation;
542   PropertyBuffer mCurveVertices;
543   PropertyBuffer mLine1Vertices;
544   PropertyBuffer mLine2Vertices;
545   Vector2 mRelativeDragPoint;
546   Vector2 mLastControlPointPosition1;
547   Vector2 mLastControlPointPosition2;
548   Property::Index mPositionFactorIndex;
549   float mDuration;
550   unsigned int mControlPoint1Id;
551   unsigned int mControlPoint2Id;
552   float mControlPointScale;
553   float mControlPointZoomScale;
554   bool mGoingRight;
555 };
556
557
558 int main( int argc, char **argv )
559 {
560   Application application = Application::New( &argc, &argv );
561
562   BezierCurveExample test( application );
563   application.MainLoop();
564   return 0;
565 }