Merge remote-tracking branch 'origin/tizen' into new_text
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / page-turn-view / page-turn-view-impl.cpp
1 /*
2  * Copyright (c) 2014 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 // CLASS HEADER
19 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-view-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/animation/animation.h>
23 #include <dali/public-api/animation/constraint.h>
24 #include <dali/public-api/events/hit-test-algorithm.h>
25 #include <dali/public-api/object/type-registry.h>
26 #include <dali/public-api/object/type-registry-helper.h>
27 #include <dali/public-api/render-tasks/render-task-list.h>
28
29 // INTERNAL INCLUDES
30 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
31
32 using namespace Dali;
33
34 namespace //Unnamed namespace
35 {
36 // To register type
37
38 DALI_TYPE_REGISTRATION_BEGIN( Toolkit::PageTurnView, Toolkit::Control, NULL )
39 DALI_TYPE_REGISTRATION_END()
40
41 // default grid density for page turn effect, 10 pixels by 10 pixels
42 const float DEFAULT_GRID_DENSITY(10.0f);
43
44 // to bent the page, the minimal horizontal pan start position is pageSize.x * MINIMUM_START_POSITION_RATIO
45 const float MINIMUM_START_POSITION_RATIO(0.6f);
46
47 // the maximum vertical displacement of pan gesture, if exceed, will reduce it: pageSize.y * MAXIMUM_VERTICAL_MOVEMENT_RATIO
48 const float MAXIMUM_VERTICAL_MOVEMENT_RATIO(0.15f);
49
50 // when the x component of pan position reaches pageSize.x * PAGE_TURN_OVER_THRESHOLD_RATIO, page starts to turn over
51 const float PAGE_TURN_OVER_THRESHOLD_RATIO(0.5f);
52
53 // duration of animation, shorter for faster speed
54 const float PAGE_SLIDE_BACK_ANIMATION_DURATION(1.0f);
55 const float PAGE_TURN_OVER_ANIMATION_DURATION(1.2f);
56
57 // the major&minor radius (in pixels) to form an ellipse shape
58 // the top-left quarter of this ellipse is used to calculate spine normal for simulating shadow
59 const Vector2 DEFAULT_SPINE_SHADOW_PARAMETER(50.0f, 20.0f);
60
61 // constants for shadow casting
62 const float POINT_LIGHT_HEIGHT_RATIO(2.f);
63 const Vector4 DEFAULT_SHADOW_COLOR = Vector4(0.2f, 0.2f, 0.2f, 0.5f);
64
65 // constraints ////////////////////////////////////////////////////////////////
66 /**
67  * Original Center Constraint
68  *
69  * This constraint adjusts the original center property of the page turn shader effect
70  * based on the X-direction displacement of the pan gesture
71  */
72 struct OriginalCenterConstraint
73 {
74   OriginalCenterConstraint(const Vector2& originalCenter, const Vector2& offset)
75   : mOldCenter( originalCenter )
76   {
77     mNewCenter = originalCenter + offset;
78     mDistance = offset.Length() * 0.5f;
79     mDirection = offset  / mDistance;
80   }
81
82   void operator()( Vector2& current, const PropertyInputContainer& inputs )
83   {
84     float displacement = inputs[0]->GetFloat();
85
86     if( displacement < mDistance )
87     {
88       current = mOldCenter + mDirection * displacement;
89     }
90     else
91     {
92       current = mNewCenter + Vector2(0.25f*(displacement-mDistance), 0.f);
93     }
94   }
95
96   Vector2 mOldCenter;
97   Vector2 mNewCenter;
98   float mDistance;
99   Vector2 mDirection;
100 };
101
102 /**
103  * Rotation Constraint
104  *
105  * This constraint adjusts the rotation property of the page actor
106  * based on the X-direction displacement of the pan gesture
107  */
108 struct RotationConstraint
109 {
110   RotationConstraint( float distance, float pageWidth, bool isTurnBack )
111   : mDistance( distance*0.5f )
112   {
113     mStep = 1.f / pageWidth;
114     mSign = isTurnBack ? -1.0f : 1.0f;
115     mConst = isTurnBack ? -1.0f : 0.f;
116     mRotation = isTurnBack ? Quaternion( -Math::PI, Vector3::YAXIS ) : Quaternion( 0.f, Vector3::YAXIS );
117   }
118
119   void operator()( Quaternion& current, const PropertyInputContainer& inputs )
120   {
121     float displacement = inputs[0]->GetFloat();
122     if( displacement < mDistance)
123     {
124       current = mRotation;
125     }
126     else
127     {
128       float coef = std::max(-1.0f, mStep*(mDistance-displacement));
129       float angle = Math::PI * ( mConst + mSign * coef );
130       current = Quaternion( angle, Vector3::YAXIS );
131     }
132   }
133
134   float mDistance;
135   float mStep;
136   float mConst;
137   float mSign;
138   Quaternion mRotation;
139 };
140
141 /**
142  * Current Center Constraint
143  *
144  * This constraint adjusts the current center property of the page turn shader effect
145  * based on the pan position and the original center position
146  */
147 struct CurrentCenterConstraint
148 {
149   CurrentCenterConstraint( float pageWidth )
150   : mPageWidth( pageWidth )
151   {
152     mThres = pageWidth * PAGE_TURN_OVER_THRESHOLD_RATIO * 0.5f;
153   }
154
155   void operator()( Vector2& current, const PropertyInputContainer& inputs )
156   {
157     const Vector2& centerPosition = inputs[0]->GetVector2();
158     if( centerPosition.x > 0.f )
159     {
160       current.x = mThres+centerPosition.x * 0.5f;
161       current.y = centerPosition.y;
162     }
163     else
164     {
165       const Vector2& centerOrigin = inputs[1]->GetVector2();
166       Vector2 direction = centerOrigin - Vector2(mThres, centerPosition.y);
167       float coef = 1.f+(centerPosition.x*2.f / mPageWidth);
168       // Todo: when coef <= 0, the page is flat, slow down the last moment of the page stretch by 10 times to avoid a small bounce
169       if(coef < 0.025f)
170       {
171         coef = (coef+0.225f)/10.0f;
172       }
173       current = centerOrigin - direction * coef;
174     }
175   }
176
177   float mPageWidth;
178   float mThres;
179 };
180
181 struct ShadowBlurStrengthConstraint
182 {
183   ShadowBlurStrengthConstraint( float thres )
184   : mThres( thres )
185   {}
186
187   void operator()( float& blurStrength,  const PropertyInputContainer& inputs )
188   {
189     float displacement = inputs[2]->GetFloat();
190     if( EqualsZero(displacement))
191     {
192       const Vector2& cur = inputs[0]->GetVector2();
193       const Vector2& ori = inputs[1]->GetVector2();
194       blurStrength =  5.f*(ori-cur).Length() / mThres;
195     }
196     else
197     {
198       blurStrength =  1.f - (displacement-2.f*mThres)/mThres;
199     }
200
201     blurStrength = blurStrength > 1.f ? 1.f : ( blurStrength < 0.f ? 0.f : blurStrength );
202   }
203
204   float mThres;
205 };
206
207 bool IsActorHittableFunction( Actor actor, Dali::HitTestAlgorithm::TraverseType type )
208 {
209   bool hittable = false;
210
211   switch (type)
212   {
213     case Dali::HitTestAlgorithm::CHECK_ACTOR:
214     {
215       // Check whether the actor is visible and not fully transparent.
216       Property::Index propertyActorHittable = actor.GetPropertyIndex(Toolkit::PageFactory::ACTOR_HITTABLE);
217       if( actor.IsSensitive()
218        && actor.IsVisible()
219        && actor.GetCurrentWorldColor().a > 0.01f// not FULLY_TRANSPARENT
220        && ( propertyActorHittable != Property::INVALID_INDEX &&
221             actor.GetProperty<bool>( propertyActorHittable ) ) )
222       {
223          hittable = true;
224       }
225       break;
226     }
227     case Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE:
228     {
229       if( actor.IsSensitive() && actor.IsVisible() ) // Actor is visible, if not visible then none of its children are visible.
230       {
231         hittable = true;
232       }
233       break;
234     }
235     default:
236     {
237       break;
238     }
239   }
240
241   return hittable;
242 }
243
244 } //unnamed namespace
245
246 namespace Dali
247 {
248
249 namespace Toolkit
250 {
251
252 namespace Internal
253 {
254
255 // these several constants are also used in the derived classes
256 const int PageTurnView::MAXIMUM_TURNING_NUM = 4;
257 const int PageTurnView::NUMBER_OF_CACHED_PAGES_EACH_SIDE = MAXIMUM_TURNING_NUM + 1;
258 const int PageTurnView::NUMBER_OF_CACHED_PAGES = NUMBER_OF_CACHED_PAGES_EACH_SIDE*2;
259 const float PageTurnView::STATIC_PAGE_INTERVAL_DISTANCE = 1.0f;
260
261 PageTurnView::PageTurnView( PageFactory& pageFactory, const Vector2& pageSize )
262 : Control( REQUIRES_TOUCH_EVENTS ),
263   mPageFactory( pageFactory ),
264   mPageSize( pageSize ),
265   mTotalPageCount( 0 ),
266   mIsEditMode( false ),
267   mNeedOffscreenRendering( false ),
268   mPanning( false ),
269   mSpineShadowParameter( DEFAULT_SPINE_SHADOW_PARAMETER ),
270   mCurrentPageIndex( 0 ),
271   mIndex( 0 ),
272   mPress( false ),
273   mPageUpdated( true ),
274   mDistanceUpCorner( 0.f ),
275   mDistanceBottomCorner( 0.f ),
276   mPanDisplacement( 0.f ),
277   mConstraints( false ),
278   mPageTurnStartedSignal(),
279   mPageTurnFinishedSignal(),
280   mPagePanStartedSignal(),
281   mPagePanFinishedSignal()
282 {
283   mPageActors.resize( NUMBER_OF_CACHED_PAGES );
284   mIsAnimating.resize( MAXIMUM_TURNING_NUM );
285   mIsSliding.resize( MAXIMUM_TURNING_NUM );
286   mTurnEffect.resize( MAXIMUM_TURNING_NUM );
287   mPropertyPanDisplacement.resize( MAXIMUM_TURNING_NUM );
288   mPropertyCurrentCenter.resize( MAXIMUM_TURNING_NUM );
289 }
290
291 PageTurnView::~PageTurnView()
292 {
293 }
294
295 void PageTurnView::OnInitialize()
296 {
297    // create the two book spine effect for static images, left and right side pages respectively
298   mSpineEffectFront = PageTurnBookSpineEffect::New();
299   mSpineEffectFront.SetIsBackImageVisible( false );
300   mSpineEffectFront.SetPageWidth( mPageSize.width );
301   mSpineEffectFront.SetShadowWidth( 0.f );
302   mSpineEffectFront.SetSpineShadowParameter( mSpineShadowParameter );
303
304   mSpineEffectBack = PageTurnBookSpineEffect::New();
305   mSpineEffectBack.SetIsBackImageVisible( true );
306   mSpineEffectBack.SetPageWidth( mPageSize.width );
307   mSpineEffectBack.SetShadowWidth( 0.f );
308   mSpineEffectBack.SetSpineShadowParameter( mSpineShadowParameter );
309
310   // create the page turn effect objects
311   for( int i = 0; i < MAXIMUM_TURNING_NUM; i++ )
312   {
313     mTurnEffect[i] = Toolkit::PageTurnEffect::New( false );
314     mTurnEffect[i].SetProperty( ShaderEffect::Property::GRID_DENSITY, Property::Value( DEFAULT_GRID_DENSITY ) );
315     mTurnEffect[i].SetPageSize( mPageSize );
316     mTurnEffect[i].SetShadowWidth(0.f);
317     mTurnEffect[i].SetSpineShadowParameter( mSpineShadowParameter );
318     mIsAnimating[i] = false;
319     mIsSliding[i] = false;
320     mPropertyPanDisplacement[i] = Self().RegisterProperty("PAN_DISPLACEMENT_PROPERTY_"+i, 0.0f);
321     mPropertyCurrentCenter[i] = Self().RegisterProperty("CURRENT_CENTER_PROPERTY_"+i, Vector2(0.0f,0.0f));
322   }
323
324   mTurningPageLayer = Layer::New();
325   mTurningPageLayer.SetAnchorPoint( AnchorPoint::CENTER_LEFT );
326   // Set control size and the parent origin of turningPageLayer
327   OnPageTurnViewInitialize();
328
329   mRootOnScreen = Actor::New();
330   mRootOnScreen.SetPositionInheritanceMode( USE_PARENT_POSITION );
331   mRootOnScreen.SetSize( mControlSize );
332   Self().Add( mRootOnScreen );
333   mRootOnScreen.Add(mTurningPageLayer);
334
335   mTotalPageCount = static_cast<int>( mPageFactory.GetNumberOfPages() );
336   mNeedOffscreenRendering = mPageFactory.IsOffscreenRenderingNeeded();
337   if( mNeedOffscreenRendering )
338   {
339     SetupRenderTasks();
340   }
341
342   // add pages to the scene, and set depth for the stacked pages
343   for( int i = 0; i < NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++ )
344   {
345     AddPage( i );
346     if(mPageActors[i])
347     {
348       mPageActors[i].SetZ( -static_cast<float>( i )*STATIC_PAGE_INTERVAL_DISTANCE );
349     }
350   }
351
352   // enable the pan gesture which is attached to the control
353   EnableGestureDetection(Gesture::Type(Gesture::Pan));
354
355   mPageFactory.PageRefreshSignal().Connect(this, &PageTurnView::RenderPage);
356 }
357
358 void PageTurnView::SetupRenderTasks()
359 {
360   mPageSourceActor.resize( NUMBER_OF_CACHED_PAGES );
361   mOffscreenTask.resize( NUMBER_OF_CACHED_PAGES );
362   mRenderedPage.resize( NUMBER_OF_CACHED_PAGES );
363
364   mCameraActor = CameraActor::New(mControlSize);
365   mCameraActor.SetParentOrigin(ParentOrigin::CENTER);
366   mCameraActor.SetPositionInheritanceMode( DONT_INHERIT_POSITION );
367   mCameraActor.SetInheritScale( false );
368   Self().Add(mCameraActor);
369
370   RenderTaskList taskList = Stage::GetCurrent().GetRenderTaskList();
371   for(int i=0; i<NUMBER_OF_CACHED_PAGES; i++)
372   {
373     mPageSourceActor[i] = Actor::New();
374     mPageSourceActor[i].SetParentOrigin(ParentOrigin::CENTER);
375     mPageSourceActor[i].SetColorMode( USE_OWN_COLOR );
376     mPageSourceActor[i].SetPositionInheritanceMode( DONT_INHERIT_POSITION );
377     mPageSourceActor[i].SetInheritScale( false );
378     Self().Add( mPageSourceActor[i] );
379     mPageSourceActor[i].SetSensitive( false );
380
381     mRenderedPage[i] = FrameBufferImage::New( mControlSize.width, mControlSize.height, Pixel::RGB8888, Image::UNUSED );
382     mOffscreenTask[i] = taskList.CreateTask();
383     mOffscreenTask[i].SetRefreshRate( RenderTask::REFRESH_ONCE );
384     mOffscreenTask[i].SetCameraActor(mCameraActor);
385     mOffscreenTask[i].SetSourceActor( mPageSourceActor[i] );
386     mOffscreenTask[i].SetExclusive(true);
387     mOffscreenTask[i].SetInputEnabled( false );
388     mOffscreenTask[i].SetClearEnabled( true );
389     mOffscreenTask[i].SetClearColor( Vector4(0.f,0.f,0.f,0.f) );
390     mOffscreenTask[i].SetTargetFrameBuffer( mRenderedPage[i] );
391     mOffscreenTask[i].SetScreenToFrameBufferMappingActor( Self() );
392    }
393 }
394
395 void PageTurnView::SetupShadowView()
396 {
397   mShadowView = Toolkit::ShadowView::New( 0.25f, 0.25f );
398   Vector3 origin = mTurningPageLayer.GetCurrentParentOrigin();
399   mShadowView.SetParentOrigin( origin );
400   mShadowView.SetAnchorPoint( origin );
401   mShadowView.SetPointLightFieldOfView( Math::PI / 2.0f);
402   mShadowView.SetShadowColor(DEFAULT_SHADOW_COLOR);
403
404   mShadowLayer = Layer::New();
405   mShadowLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
406   mRootOnScreen.Add(mShadowLayer);
407   mShadowLayer.Raise();
408
409   mShadowPlane = CreateSolidColorActor( Vector4 (0.0f, 0.0f, 0.0f, 0.0f) );
410   mShadowPlane.SetPositionInheritanceMode( USE_PARENT_POSITION_PLUS_LOCAL_POSITION );
411   mShadowPlane.SetSize( mControlSize );
412   mShadowLayer.Add( mShadowPlane );
413   mShadowView.SetShadowPlane( mShadowPlane );
414
415   mPointLight = Actor::New();
416   mPointLight.SetAnchorPoint( origin );
417   mPointLight.SetParentOrigin( origin );
418   mPointLight.SetPosition( 0.f, 0.f, mPageSize.width*POINT_LIGHT_HEIGHT_RATIO );
419   mRootOnScreen.Add( mPointLight );
420   mShadowView.SetPointLight( mPointLight );
421
422   mTurningPageLayer.Add( mShadowView );
423   mShadowView.Activate();
424 }
425
426 void PageTurnView::OnControlStageConnection()
427 {
428   SetupShadowView();
429   mTurningPageLayer.RaiseToTop();
430 }
431
432 void PageTurnView::OnControlStageDisconnection()
433 {
434   if(mShadowView)
435   {
436     Self().Remove(mPointLight);
437     Self().Remove(mShadowLayer);
438     mTurningPageLayer.Remove( mShadowView );
439   }
440
441   // make sure the status of the control is updated correctly when the pan gesture is interrupted
442   if(mPanning)
443   {
444     mPanning = false;
445
446     mRootOnScreen.Add(mPanActor);
447     mIsAnimating[mIndex] = false;
448     mPanActor.RemoveConstraints();
449     mTurnEffect[mIndex].RemoveConstraints();
450     mPageUpdated = true;
451
452     SetSpineEffect( mPanActor, mIsTurnBack[mPanActor] );
453   }
454 }
455
456 void PageTurnView::OnControlSizeSet( const Vector3& size )
457 {
458   // disable the SetSize of the control from the application
459   Self().SetSize( mControlSize );
460 }
461
462 void PageTurnView::SetSpineShadowParameter( const Vector2& spineShadowParameter )
463 {
464   mSpineShadowParameter = spineShadowParameter;
465
466   // set spine shadow parameter to all the shader effects
467   mSpineEffectFront.SetSpineShadowParameter( mSpineShadowParameter );
468   mSpineEffectBack.SetSpineShadowParameter( mSpineShadowParameter );
469   for( int i = 0; i < MAXIMUM_TURNING_NUM; i++ )
470   {
471     mTurnEffect[i].SetSpineShadowParameter( mSpineShadowParameter );
472   }
473 }
474
475 Vector2 PageTurnView::GetSpineShadowParameter()
476 {
477   return mSpineShadowParameter;
478 }
479
480 void PageTurnView::GoToPage( unsigned int pageId )
481 {
482   int pageIdx = static_cast<int>(pageId);
483   // record the new current page index
484   mCurrentPageIndex = pageIdx;
485
486   // clear the old pages
487   for(int i = 0; i < NUMBER_OF_CACHED_PAGES; i++ )
488   {
489     if( mPageActors[i] )
490     {
491       mPageActors[i].Unparent();
492       mPageActors[i].Reset();
493     }
494   }
495
496   // add the current page and the pages right before and after it
497   for( int i = pageIdx - NUMBER_OF_CACHED_PAGES_EACH_SIDE; i < pageIdx + NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++ )
498   {
499     AddPage( i );
500   }
501   // set ordered depth to the stacked pages
502   OrganizePageDepth();
503 }
504
505 unsigned int PageTurnView::GetCurrentPage()
506 {
507   DALI_ASSERT_ALWAYS( mCurrentPageIndex >= 0 );
508   return static_cast< unsigned int >( mCurrentPageIndex );
509 }
510
511 Actor PageTurnView::EnterEditMode()
512 {
513   if( mNeedOffscreenRendering )
514   {
515     DALI_ASSERT_ALWAYS( mCurrentPageIndex >= 0 );
516
517     mIsEditMode = true;
518
519     int index = mCurrentPageIndex % NUMBER_OF_CACHED_PAGES;
520     mOffscreenTask[index].SetInputEnabled( true );
521     mPageSourceActor[index].SetSensitive( true );
522     mOffscreenTask[index].SetRefreshRate( RenderTask::REFRESH_ALWAYS );
523
524     mRootOnScreen.SetSensitive(false);
525
526     return mPageSourceActor[index].GetChildAt( 0 );
527   }
528   else
529   {
530     return Actor();
531   }
532 }
533
534 void PageTurnView::LeaveEditMode()
535 {
536   if( mNeedOffscreenRendering )
537   {
538     DALI_ASSERT_ALWAYS( mCurrentPageIndex >= 0 );
539
540     mIsEditMode = false;
541
542     int index = mCurrentPageIndex % NUMBER_OF_CACHED_PAGES;
543     mOffscreenTask[index].SetInputEnabled( false );
544     mPageSourceActor[index].SetSensitive( false );
545     mOffscreenTask[index].SetRefreshRate( RenderTask::REFRESH_ONCE );
546
547     mRootOnScreen.SetSensitive(true);
548   }
549 }
550
551 Actor PageTurnView::GetHitActor( Vector2& screenCoordinates, Vector2& actorCoordinates )
552 {
553   if( mNeedOffscreenRendering && mCurrentPageIndex < mTotalPageCount)
554   {
555     int index = mCurrentPageIndex % NUMBER_OF_CACHED_PAGES;
556
557     Dali::HitTestAlgorithm::Results results;
558     if( !mOffscreenTask[index].GetInputEnabled() )
559     {
560       mOffscreenTask[index].SetInputEnabled( true );
561       mPageSourceActor[index].SetSensitive( true );
562       Dali::HitTestAlgorithm::HitTest( mOffscreenTask[index], screenCoordinates, results, IsActorHittableFunction );
563       mOffscreenTask[index].SetInputEnabled( false );
564       mPageSourceActor[index].SetSensitive( false );
565     }
566     else
567     {
568       Dali::HitTestAlgorithm::HitTest( mOffscreenTask[index], screenCoordinates, results, IsActorHittableFunction );
569     }
570     actorCoordinates = results.actorCoordinates;
571     return results.actor;
572   }
573   else
574   {
575     return Actor();
576   }
577 }
578
579 void PageTurnView::AddPage( int pageIndex )
580 {
581   if(pageIndex > -1  && pageIndex < mTotalPageCount) // whether the page is available from the page factory
582   {
583     int index = pageIndex % NUMBER_OF_CACHED_PAGES;
584     ImageActor newPage;
585     if( mNeedOffscreenRendering )
586     {
587       Actor source = mPageFactory.NewPage( pageIndex );
588       if( mPageSourceActor[index].GetChildCount() > 0 )
589       {
590         mPageSourceActor[index].Remove( mPageSourceActor[index].GetChildAt( 0 ) );
591       }
592       mPageSourceActor[index].Add( source );
593       mOffscreenTask[index].SetRefreshRate( RenderTask::REFRESH_ONCE );
594       newPage = NewPageFromRenderBuffer( pageIndex );
595     }
596     else
597     {
598       newPage= ImageActor::DownCast( mPageFactory.NewPage( pageIndex ) );
599       DALI_ASSERT_ALWAYS( newPage );
600     }
601     newPage.SetAnchorPoint( AnchorPoint::CENTER_LEFT );
602     newPage.SetParentOrigin( ParentOrigin::CENTER_LEFT );
603     newPage.SetSize( mPageSize );
604     mRootOnScreen.Add( newPage );
605     mPageActors[index] = newPage;
606
607     bool isLeftSide = ( pageIndex < mCurrentPageIndex );
608     mIsTurnBack[ newPage ] = isLeftSide;
609     if( isLeftSide )
610     {
611       // new page is added to the left side, so need to rotate it 180 degrees
612       newPage.RotateBy( Degree(-180.0f ), Vector3::YAXIS );
613     }
614     else
615     {
616       SetShaderEffect( newPage, mSpineEffectFront);
617     }
618
619     // For Portrait, nothing to do
620     // For Landscape, set spineEffectBack to the new effect if it is in the left side, and set properties to the back image actor if it exists
621     OnAddPage( newPage, isLeftSide );
622   }
623 }
624
625 void PageTurnView::RemovePage( int pageIndex )
626 {
627   if( pageIndex > -1 && pageIndex < mTotalPageCount)
628   {
629     int index = pageIndex % NUMBER_OF_CACHED_PAGES;
630     mPageActors[index].Unparent();
631     mIsTurnBack.erase( mPageActors[index] );
632     mPageActors[index].Reset();
633     if( mNeedOffscreenRendering )
634     {
635       mPageSourceActor[index].Remove( mPageSourceActor[index].GetChildAt( 0 ) );
636     }
637   }
638 }
639
640 void PageTurnView::RenderPage( int pageIndex )
641 {
642   if( pageIndex > std::max(-1, mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE -1)
643    && pageIndex < std::min(mTotalPageCount, mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE))
644   {
645     int index = pageIndex % NUMBER_OF_CACHED_PAGES;
646     mOffscreenTask[index].SetRefreshRate( RenderTask::REFRESH_ONCE );
647   }
648 }
649
650 void PageTurnView::RefreshAll()
651 {
652   mTotalPageCount = static_cast<int>( mPageFactory.GetNumberOfPages() );
653   if( mTotalPageCount > 0 )
654   {
655     if(mCurrentPageIndex < mTotalPageCount)
656     {
657       GoToPage( mCurrentPageIndex );
658     }
659     else
660     {
661       GoToPage( mCurrentPageIndex-- );
662     }
663   }
664 }
665
666 void PageTurnView::RefreshCurrentPage()
667 {
668   RenderPage( mCurrentPageIndex );
669 }
670
671 void PageTurnView::OnPan( const PanGesture& gesture )
672 {
673   if( mIsEditMode )
674   {
675     // when interrupted by the call of DisplayCurrentPageSourceActor(),
676     // make sure the panFinished is always called before stopping to responding the gesture
677     // so the status of the control is updated correctly
678     if(mPanning)
679     {
680       mPanning = false;
681       PanFinished( SetPanPosition( gesture.position ), gesture.GetSpeed() );
682     }
683
684     return;
685   }
686   // the pan gesture is attached to control itself instead of each page
687   switch( gesture.state )
688   {
689     case Gesture::Started:
690     {
691       mPanning = true;
692       // to find out whether the undergoing turning page number already reaches the maximum allowed
693       // and get one idle index when it is animatable
694       bool animatable = false;
695       for( int i = 0; i < MAXIMUM_TURNING_NUM; i++ )
696       {
697         if( !mIsAnimating[mIndex] )
698         {
699           animatable = true;
700           break;
701         }
702         if( mIsSliding[mIndex] )
703         {
704           animatable = false;
705           break;
706         }
707         mIndex++;
708         mIndex = mIndex % MAXIMUM_TURNING_NUM;
709       }
710
711       if( mPageUpdated && animatable )
712       {
713         SetPanActor( gesture.position ); // determine which page actor is panned
714         if(mPanActor && mPanActor.GetParent() != mRootOnScreen) // if the page is added to turning layer,it is undergoing an animation currently
715         {
716           mPanActor.Reset();
717         }
718         PanStarted( SetPanPosition( gesture.position ) );  // pass in the pan position in the local page coordinate
719       }
720       else
721       {
722         mPanActor.Reset();
723       }
724       break;
725     }
726     case Gesture::Continuing:
727     {
728       PanContinuing( SetPanPosition( gesture.position ) ); // pass in the pan position in the local page coordinate
729       break;
730     }
731     case Gesture::Finished:
732     case Gesture::Cancelled:
733     {
734       mPanning = false;
735       PanFinished( SetPanPosition( gesture.position ), gesture.GetSpeed() );
736       break;
737     }
738     case Gesture::Clear:
739     case Gesture::Possible:
740     default:
741     {
742       break;
743     }
744   }
745 }
746
747 void PageTurnView::PanStarted( const Vector2& gesturePosition )
748 {
749   mPressDownPosition = gesturePosition;
750
751   if( !mPanActor )
752   {
753     return;
754   }
755
756   mOriginalCenter = gesturePosition;
757   mTurnEffect[mIndex].SetIsTurningBack( mIsTurnBack[ mPanActor] );
758   mPress = false;
759   mPageUpdated = false;
760
761   // Guard against destruction during signal emission
762   Toolkit::PageTurnView handle( GetOwner() );
763   mPagePanStartedSignal.Emit( handle );
764 }
765
766 void PageTurnView::PanContinuing( const Vector2& gesturePosition )
767 {
768   if( !mPanActor )
769   {
770     return;
771   }
772
773   // Guard against destruction during signal emission
774   Toolkit::PageTurnView handle( GetOwner() );
775
776   if(!mPress)
777   {
778     // when the touch down position is near the spine
779     // or when the panning goes outwards or some other position which would tear the paper in real situation
780     // we change the start position into the current panning position and update the shader parameters
781     if( mOriginalCenter.x <  mPageSize.width*MINIMUM_START_POSITION_RATIO
782         || gesturePosition.x > mOriginalCenter.x-1.0f
783         || ( ( gesturePosition.x/mOriginalCenter.x > gesturePosition.y/mOriginalCenter.y ) &&
784              ( gesturePosition.x/mOriginalCenter.x > (gesturePosition.y-mPageSize.height )/(mOriginalCenter.y-mPageSize.height ) ) ) )
785     {
786       mOriginalCenter = gesturePosition;
787     }
788     else
789     {
790       mDistanceUpCorner = mOriginalCenter.Length();
791       mDistanceBottomCorner = ( mOriginalCenter - Vector2( 0.0f, mPageSize.height ) ).Length();
792       mShadowView.Add( mPanActor );
793       SetShaderEffect( mPanActor, mTurnEffect[mIndex] );
794       mTurnEffect[mIndex].SetOriginalCenter( mOriginalCenter );
795       mCurrentCenter = mOriginalCenter;
796       mTurnEffect[mIndex].SetCurrentCenter( mCurrentCenter );
797       mPanDisplacement = 0.f;
798       mConstraints = true;
799       mPress = true;
800       mIsAnimating[mIndex] = true;
801
802       mPageTurnStartedSignal.Emit( handle, static_cast<unsigned int>(mCurrentPageIndex + ( ( mIsTurnBack[mPanActor] ) ? -1 : 0 ) ), !mIsTurnBack[mPanActor] );
803
804       mShadowView.RemoveConstraints();
805       Actor self = Self();
806       self.SetProperty( mPropertyPanDisplacement[mIndex], 0.f );
807
808       Constraint shadowBlurStrengthConstraint = Constraint::New<float>( mShadowView, mShadowView.GetBlurStrengthPropertyIndex(), ShadowBlurStrengthConstraint( mPageSize.width*PAGE_TURN_OVER_THRESHOLD_RATIO ) );
809       shadowBlurStrengthConstraint.AddSource( Source(mTurnEffect[mIndex],  mTurnEffect[mIndex].GetPropertyIndex(mTurnEffect[mIndex].PageTurnEffect::GetCurrentCenterPropertyName())) );
810       shadowBlurStrengthConstraint.AddSource( Source(mTurnEffect[mIndex],  mTurnEffect[mIndex].GetPropertyIndex(mTurnEffect[mIndex].PageTurnEffect::GetOriginalCenterPropertyName())) );
811       shadowBlurStrengthConstraint.AddSource( Source( self, mPropertyPanDisplacement[mIndex] ) );
812       shadowBlurStrengthConstraint.Apply();
813     }
814   }
815   else
816   {
817     Vector2 currentCenter = gesturePosition;
818
819     // Test whether the new current center would tear the paper from the top pine in real situation
820     // we do not forbid this totally, which would restrict the panning gesture too much
821     // instead, set it to the nearest allowable position
822     float distanceUpCorner = currentCenter.Length();
823     float distanceBottomCorner = ( currentCenter-Vector2( 0.0f, mPageSize.height ) ).Length();
824     if( distanceUpCorner > mDistanceUpCorner )
825     {
826       currentCenter = currentCenter*mDistanceUpCorner/distanceUpCorner;
827     }
828     // would tear the paper from the bottom spine in real situation
829     if( distanceBottomCorner > mDistanceBottomCorner )
830     {
831       currentCenter = ( ( currentCenter - Vector2( 0.0f, mPageSize.height ) )*mDistanceBottomCorner/distanceBottomCorner + Vector2(0.0f,mPageSize.height ) );
832     }
833     // If direction has a very high y component, reduce it.
834     Vector2 curveDirection = currentCenter - mOriginalCenter;
835     if( fabs( curveDirection.y ) > fabs( curveDirection.x ) )
836     {
837       currentCenter.y = mOriginalCenter.y + ( currentCenter.y - mOriginalCenter.y ) * fabs( curveDirection.x/curveDirection.y );
838     }
839     // If the vertical distance is high, reduce it
840     float yShift = currentCenter.y - mOriginalCenter.y;
841     if( fabs( yShift ) > mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO )
842     {
843       currentCenter.y = mOriginalCenter.y + yShift*mPageSize.height*MAXIMUM_VERTICAL_MOVEMENT_RATIO/fabs(yShift );
844     }
845
846     // use contraints to control the page shape and rotation when the pan position is near the spine
847     if( currentCenter.x <= mPageSize.width*PAGE_TURN_OVER_THRESHOLD_RATIO && mOriginalCenter.x > mPageSize.width*PAGE_TURN_OVER_THRESHOLD_RATIO )
848     {
849       // set the property values used by the constraints
850       mPanDisplacement = mPageSize.width*PAGE_TURN_OVER_THRESHOLD_RATIO - currentCenter.x;
851       Self().SetProperty( mPropertyPanDisplacement[mIndex], mPanDisplacement );
852       Self().SetProperty( mPropertyCurrentCenter[mIndex], currentCenter );
853
854       // set up the OriginalCenterConstraint and CurrentCebterConstraint to the PageTurnEdffect
855       // also set up the RotationConstraint to the page actor
856       if( mConstraints )
857       {
858         Vector2 corner;
859         // the corner position need to be a little far away from the page edge to ensure the whole page is lift up
860         if( currentCenter.y >= mOriginalCenter.y )
861         {
862           corner = Vector2( 1.1f*mPageSize.width, 0.f );
863         }
864         else
865         {
866           corner = mPageSize*1.1f;
867         }
868
869         Vector2 offset( currentCenter-mOriginalCenter );
870         float k = - ( (mOriginalCenter.x-corner.x)*offset.x + (mOriginalCenter.y-corner.y)*offset.y )
871                    /( offset.x*offset.x + offset.y*offset.y );
872         offset *= k;
873         Actor self = Self();
874
875         Property::Index shaderOriginalCenterPropertyIndex = mTurnEffect[mIndex].GetPropertyIndex(mTurnEffect[mIndex].PageTurnEffect::GetOriginalCenterPropertyName());
876         Constraint originalCenterConstraint = Constraint::New<Vector2>( mTurnEffect[mIndex], shaderOriginalCenterPropertyIndex, OriginalCenterConstraint( mOriginalCenter, offset ));
877         originalCenterConstraint.AddSource( Source( self, mPropertyPanDisplacement[mIndex] ) );
878         originalCenterConstraint.Apply();
879
880         Property::Index shaderCurrentCenterPropertyIndex = mTurnEffect[mIndex].GetPropertyIndex(mTurnEffect[mIndex].PageTurnEffect::GetCurrentCenterPropertyName());
881         Constraint currentCenterConstraint = Constraint::New<Vector2>( mTurnEffect[mIndex], shaderCurrentCenterPropertyIndex, CurrentCenterConstraint(mPageSize.width));
882         currentCenterConstraint.AddSource( Source(self, mPropertyCurrentCenter[mIndex]) );
883         currentCenterConstraint.AddSource( Source(mTurnEffect[mIndex], shaderOriginalCenterPropertyIndex) );
884         currentCenterConstraint.Apply();
885
886         GetImpl( mTurnEffect[mIndex] ).ApplyInternalConstraint();
887
888         float distance = offset.Length();
889         Constraint rotationConstraint = Constraint::New<Quaternion>( mPanActor, Actor::Property::ORIENTATION, RotationConstraint(distance, mPageSize.width, mIsTurnBack[mPanActor]));
890         rotationConstraint.AddSource( Source( self, mPropertyPanDisplacement[mIndex] ) );
891         rotationConstraint.Apply();
892
893         mConstraints = false;
894       }
895     }
896     else
897     {
898       if(!mConstraints) // remove the constraint is the pan position move back to far away from the spine
899       {
900         mPanActor.RemoveConstraints();
901         mTurnEffect[mIndex].RemoveConstraints();
902         mTurnEffect[mIndex].SetOriginalCenter( mOriginalCenter );
903         mConstraints = true;
904         mPanDisplacement = 0.f;
905       }
906
907       mTurnEffect[mIndex].SetCurrentCenter( currentCenter );
908       mCurrentCenter = currentCenter;
909       GetImpl( mTurnEffect[mIndex] ).ApplyInternalConstraint();
910     }
911   }
912 }
913
914 void PageTurnView::PanFinished( const Vector2& gesturePosition, float gestureSpeed )
915 {
916   // Guard against destruction during signal emission
917   Toolkit::PageTurnView handle( GetOwner() );
918
919   if( !mPanActor )
920   {
921     if(!mIsAnimating[mIndex])
922     {
923       OnPossibleOutwardsFlick( gesturePosition, gestureSpeed );
924     }
925     return;
926   }
927
928   mPagePanFinishedSignal.Emit( handle );
929
930   ImageActor actor = mPanActor;
931   if(mPress)
932   {
933     if(!mConstraints) // if with constraints, the pan finished position is near spine, set up an animation to turn the page over
934     {
935       // update the pages here instead of in the TurnedOver callback function
936       // as new page is allowed to respond to the pan gesture before other pages finishing animation
937       if(mIsTurnBack[actor])
938       {
939         mCurrentPageIndex--;
940         RemovePage( mCurrentPageIndex+NUMBER_OF_CACHED_PAGES_EACH_SIDE );
941         AddPage( mCurrentPageIndex-NUMBER_OF_CACHED_PAGES_EACH_SIDE );
942       }
943       else
944       {
945         mCurrentPageIndex++;
946         RemovePage( mCurrentPageIndex-NUMBER_OF_CACHED_PAGES_EACH_SIDE-1 );
947         AddPage( mCurrentPageIndex+NUMBER_OF_CACHED_PAGES_EACH_SIDE-1 );
948       }
949       OrganizePageDepth();
950
951       // set up an animation to turn the page over
952       Actor self = Self();
953       float width = mPageSize.width*(1.f+PAGE_TURN_OVER_THRESHOLD_RATIO);
954       Animation animation = Animation::New( std::max(0.1f,PAGE_TURN_OVER_ANIMATION_DURATION * (1.0f - mPanDisplacement / width)) );
955       animation.AnimateTo( Property(self, mPropertyPanDisplacement[mIndex]),
956                            width,AlphaFunctions::EaseOutSine33);
957       animation.AnimateTo( Property(self, mPropertyCurrentCenter[mIndex]),
958                            Vector2(-mPageSize.width, 0.5f*mPageSize.height), AlphaFunctions::EaseOutSine33);
959       mAnimationActorPair[animation] = actor;
960       mAnimationIndexPair[animation] = mIndex;
961       animation.Play();
962       animation.FinishedSignal().Connect( this, &PageTurnView::TurnedOver );
963     }
964     else // the pan finished position is far away from the spine, set up an animation to slide the page back instead of turning over
965     {
966       Animation animation= Animation::New( PAGE_SLIDE_BACK_ANIMATION_DURATION * (mOriginalCenter.x - mCurrentCenter.x) / mPageSize.width / PAGE_TURN_OVER_THRESHOLD_RATIO );
967       animation.AnimateTo( Property( mTurnEffect[mIndex], mTurnEffect[mIndex].PageTurnEffect::GetCurrentCenterPropertyName() ),
968                            mOriginalCenter, AlphaFunctions::Linear );
969       mAnimationActorPair[animation] = actor;
970       mAnimationIndexPair[animation] = mIndex;
971       animation.Play();
972       mIsSliding[mIndex] = true;
973       animation.FinishedSignal().Connect( this, &PageTurnView::SliddenBack );
974
975       mPageTurnStartedSignal.Emit( handle, static_cast<unsigned int>( mCurrentPageIndex + ( ( mIsTurnBack[mPanActor] ) ? -1 : 0 ) ), mIsTurnBack[mPanActor] );
976     }
977   }
978   else
979   {
980     // In portrait view, an outwards flick should turn the previous page back
981     // In landscape view, nothing to do
982     OnPossibleOutwardsFlick( gesturePosition, gestureSpeed );
983   }
984
985   mPageUpdated = true;
986 }
987
988 void PageTurnView::TurnedOver( Animation& animation )
989 {
990   ImageActor actor = mAnimationActorPair[animation];
991   mIsTurnBack[actor] = !mIsTurnBack[actor];
992   actor.RemoveConstraints();
993   mRootOnScreen.Add(actor);
994   int index = mAnimationIndexPair[animation];
995   mIsAnimating[index] = false;
996   mTurnEffect[index].RemoveConstraints();
997   mAnimationIndexPair.erase( animation );
998   mAnimationActorPair.erase( animation );
999
1000   SetSpineEffect( actor, mIsTurnBack[actor] );
1001
1002   // Guard against destruction during signal emission
1003   Toolkit::PageTurnView handle( GetOwner() );
1004   mPageTurnFinishedSignal.Emit( handle, static_cast<unsigned int>( mCurrentPageIndex + ( ( mIsTurnBack[actor] ) ? -1 : 0 ) ), mIsTurnBack[actor] );
1005 }
1006
1007 void PageTurnView::SliddenBack( Animation& animation )
1008 {
1009   ImageActor actor = mAnimationActorPair[animation];
1010   mRootOnScreen.Add(actor);
1011   int index = mAnimationIndexPair[animation];
1012   mIsSliding[index] = false;
1013   mIsAnimating[index] = false;
1014   mAnimationIndexPair.erase( animation );
1015   mAnimationActorPair.erase( animation );
1016
1017   SetSpineEffect( actor, mIsTurnBack[actor] );
1018
1019   // Guard against destruction during signal emission
1020   Toolkit::PageTurnView handle( GetOwner() );
1021   mPageTurnFinishedSignal.Emit( handle, static_cast<unsigned int>( mCurrentPageIndex + ( ( mIsTurnBack[mPanActor] ) ? -1 : 0 ) ), mIsTurnBack[actor] );
1022 }
1023
1024 void PageTurnView::OrganizePageDepth()
1025 {
1026   for( int i=0; i<NUMBER_OF_CACHED_PAGES_EACH_SIDE;i++ )
1027   {
1028     if(mCurrentPageIndex+i < mTotalPageCount)
1029     {
1030       mPageActors[( mCurrentPageIndex+i )%NUMBER_OF_CACHED_PAGES].SetZ( -static_cast<float>( i )*STATIC_PAGE_INTERVAL_DISTANCE );
1031     }
1032     if( mCurrentPageIndex >= i + 1 )
1033     {
1034       mPageActors[( mCurrentPageIndex-i-1 )%NUMBER_OF_CACHED_PAGES].SetZ( -static_cast<float>( i )*STATIC_PAGE_INTERVAL_DISTANCE );
1035     }
1036   }
1037 }
1038
1039 void PageTurnView::SetShaderEffect( ImageActor actor, ShaderEffect shaderEffect )
1040 {
1041   SetShaderEffectRecursively( actor, shaderEffect );
1042 }
1043
1044 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnStartedSignal()
1045 {
1046   return mPageTurnStartedSignal;
1047 }
1048
1049 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnFinishedSignal()
1050 {
1051   return mPageTurnFinishedSignal;
1052 }
1053
1054 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanStartedSignal()
1055 {
1056   return mPagePanStartedSignal;
1057 }
1058
1059 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanFinishedSignal()
1060 {
1061   return mPagePanFinishedSignal;
1062 }
1063
1064 } // namespace Internal
1065
1066 } // namespace Toolkit
1067
1068 } // namespace Dali