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