2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-view-impl.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/animation/animation.h>
24 #include <dali/public-api/animation/constraint.h>
25 #include <dali/public-api/object/type-registry-helper.h>
26 #include <dali/public-api/object/type-registry.h>
27 #include <cstring> // for strcmp
30 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
31 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-book-spine-effect.h>
32 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-effect.h>
33 #include <dali-toolkit/internal/visuals/visual-factory-cache.h>
34 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
35 #include <dali-toolkit/public-api/visuals/visual-properties.h>
39 namespace //Unnamed namespace
41 // properties set on shader, these properties have the constant value in regardless of the page status
42 const char* const PROPERTY_SPINE_SHADOW("uSpineShadowParameter"); // uniform for both spine and turn effect
44 // properties set on actor, the value of these properties varies depending on the page status
45 // properties used in turn effect
46 const char* const PROPERTY_TURN_DIRECTION("uIsTurningBack"); // uniform
47 const char* const PROPERTY_COMMON_PARAMETERS("uCommonParameters"); //uniform
49 const char* const PROPERTY_PAN_DISPLACEMENT("panDisplacement"); // property used to constrain the uniforms
50 const char* const PROPERTY_PAN_CENTER("panCenter"); // property used to constrain the uniforms
52 // default grid density for page turn effect, 20 pixels by 20 pixels
53 const float DEFAULT_GRID_DENSITY(20.0f);
55 // to bent the page, the minimal horizontal pan start position is viewPageSize.x * MINIMUM_START_POSITION_RATIO
56 const float MINIMUM_START_POSITION_RATIO(0.6f);
58 // the maximum vertical displacement of pan gesture, if exceed, will reduce it: viewPageSize.y * MAXIMUM_VERTICAL_MOVEMENT_RATIO
59 const float MAXIMUM_VERTICAL_MOVEMENT_RATIO(0.15f);
61 // when the x component of pan position reaches viewPageSize.x * PAGE_TURN_OVER_THRESHOLD_RATIO, page starts to turn over
62 const float PAGE_TURN_OVER_THRESHOLD_RATIO(0.5f);
64 // duration of animation, shorter for faster speed
65 const float PAGE_SLIDE_BACK_ANIMATION_DURATION(1.0f);
66 const float PAGE_TURN_OVER_ANIMATION_DURATION(1.2f);
68 // the major&minor radius (in pixels) to form an ellipse shape
69 // the top-left quarter of this ellipse is used to calculate spine normal for simulating shadow
70 const Vector2 DEFAULT_SPINE_SHADOW_PARAMETER(50.0f, 20.0f);
72 // constants for shadow casting
73 const float POINT_LIGHT_HEIGHT_RATIO(2.f);
74 const Vector4 DEFAULT_SHADOW_COLOR = Vector4(0.2f, 0.2f, 0.2f, 0.5f);
76 // constraints ////////////////////////////////////////////////////////////////
78 * Original Center Constraint
80 * This constraint adjusts the original center property of the page turn shader effect
81 * based on the X-direction displacement of the pan gesture
83 struct OriginalCenterConstraint
85 OriginalCenterConstraint(const Vector2& originalCenter, const Vector2& offset)
86 : mOldCenter(originalCenter)
88 mNewCenter = originalCenter + offset;
89 mDistance = offset.Length() * 0.5f;
90 mDirection = offset / mDistance;
93 void operator()(Vector2& current, const PropertyInputContainer& inputs)
95 float displacement = inputs[0]->GetFloat();
97 if(displacement < mDistance)
99 current = mOldCenter + mDirection * displacement;
103 current = mNewCenter + Vector2(0.25f * (displacement - mDistance), 0.f);
114 * Rotation Constraint
116 * This constraint adjusts the rotation property of the page actor
117 * based on the X-direction displacement of the pan gesture
119 struct RotationConstraint
121 RotationConstraint(float distance, float pageWidth, bool isTurnBack)
122 : mDistance(distance * 0.5f)
124 mStep = 1.f / pageWidth;
125 mSign = isTurnBack ? -1.0f : 1.0f;
126 mConst = isTurnBack ? -1.0f : 0.f;
127 mRotation = isTurnBack ? Quaternion(Radian(-Math::PI), Vector3::YAXIS) : Quaternion(Radian(0.f), Vector3::YAXIS);
130 void operator()(Quaternion& current, const PropertyInputContainer& inputs)
132 float displacement = inputs[0]->GetFloat();
133 if(displacement < mDistance)
139 float coef = std::max(-1.0f, mStep * (mDistance - displacement));
140 float angle = Math::PI * (mConst + mSign * coef);
141 current = Quaternion(Radian(angle), Vector3::YAXIS);
149 Quaternion mRotation;
153 * Current Center Constraint
155 * This constraint adjusts the current center property of the page turn shader effect
156 * based on the pan position and the original center position
158 struct CurrentCenterConstraint
160 CurrentCenterConstraint(float pageWidth)
161 : mPageWidth(pageWidth)
163 mThres = pageWidth * PAGE_TURN_OVER_THRESHOLD_RATIO * 0.5f;
166 void operator()(Vector2& current, const PropertyInputContainer& inputs)
168 const Vector2& centerPosition = inputs[0]->GetVector2();
169 if(centerPosition.x > 0.f)
171 current.x = mThres + centerPosition.x * 0.5f;
172 current.y = centerPosition.y;
176 const Vector2& centerOrigin = inputs[1]->GetVector2();
177 Vector2 direction = centerOrigin - Vector2(mThres, centerPosition.y);
178 float coef = 1.f + (centerPosition.x * 2.f / mPageWidth);
179 // when coef <= 0, the page is flat, slow down the last moment of the page stretch by 10 times to avoid a small bounce
182 coef = (coef + 0.225f) / 10.0f;
184 current = centerOrigin - direction * coef;
192 struct ShadowBlurStrengthConstraint
194 ShadowBlurStrengthConstraint(float thres)
199 void operator()(float& blurStrength, const PropertyInputContainer& inputs)
201 float displacement = inputs[2]->GetFloat();
202 if(EqualsZero(displacement))
204 const Vector2& cur = inputs[0]->GetVector2();
205 const Vector2& ori = inputs[1]->GetVector2();
206 blurStrength = 5.f * (ori - cur).Length() / mThres;
210 blurStrength = 1.f - (displacement - 2.f * mThres) / mThres;
213 blurStrength = blurStrength > 1.f ? 1.f : (blurStrength < 0.f ? 0.f : blurStrength);
219 } //unnamed namespace
231 // empty handle as we cannot create PageTurnView(but type registered for page turn signal)
235 // Setup properties, signals and actions using the type-registry.
236 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::PageTurnView, Toolkit::Control, Create);
238 DALI_PROPERTY_REGISTRATION(Toolkit, PageTurnView, "viewPageSize", VECTOR2, VIEW_PAGE_SIZE)
239 DALI_PROPERTY_REGISTRATION(Toolkit, PageTurnView, "currentPageId", INTEGER, CURRENT_PAGE_ID)
240 DALI_PROPERTY_REGISTRATION(Toolkit, PageTurnView, "spineShadow", VECTOR2, SPINE_SHADOW)
242 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pageTurnStarted", SIGNAL_PAGE_TURN_STARTED)
243 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pageTurnFinished", SIGNAL_PAGE_TURN_FINISHED)
244 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pagePanStarted", SIGNAL_PAGE_PAN_STARTED)
245 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pagePanFinished", SIGNAL_PAGE_PAN_FINISHED)
247 DALI_TYPE_REGISTRATION_END()
251 // these several constants are also used in the derived classes
252 const char* const PageTurnView::PROPERTY_TEXTURE_WIDTH("uTextureWidth"); // uniform name
253 const char* const PageTurnView::PROPERTY_ORIGINAL_CENTER("originalCenter"); // property used to constrain the uniform
254 const char* const PageTurnView::PROPERTY_CURRENT_CENTER("currentCenter"); // property used to constrain the uniform
255 const int PageTurnView::MAXIMUM_TURNING_NUM = 4;
256 const int PageTurnView::NUMBER_OF_CACHED_PAGES_EACH_SIDE = MAXIMUM_TURNING_NUM + 1;
257 const int PageTurnView::NUMBER_OF_CACHED_PAGES = NUMBER_OF_CACHED_PAGES_EACH_SIDE * 2;
258 const float PageTurnView::STATIC_PAGE_INTERVAL_DISTANCE = 1.0f;
260 PageTurnView::Page::Page()
263 actor = Actor::New();
264 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER_LEFT);
265 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER_LEFT);
266 actor.SetProperty(Actor::Property::VISIBLE, false);
268 propertyPanDisplacement = actor.RegisterProperty(PROPERTY_PAN_DISPLACEMENT, 0.f);
269 propertyPanCenter = actor.RegisterProperty(PROPERTY_PAN_CENTER, Vector2::ZERO);
271 propertyOriginalCenter = actor.RegisterProperty(PROPERTY_ORIGINAL_CENTER, Vector2::ZERO);
272 propertyCurrentCenter = actor.RegisterProperty(PROPERTY_CURRENT_CENTER, Vector2::ZERO);
273 Matrix zeroMatrix(true);
274 actor.RegisterProperty(PROPERTY_COMMON_PARAMETERS, zeroMatrix);
275 propertyTurnDirection = actor.RegisterProperty(PROPERTY_TURN_DIRECTION, -1.f);
278 void PageTurnView::Page::SetTexture(Texture texture)
282 textureSet = TextureSet::New();
284 textureSet.SetTexture(0u, texture);
287 void PageTurnView::Page::UseEffect(Shader newShader)
292 renderer.SetShader(shader);
296 void PageTurnView::Page::UseEffect(Shader newShader, Geometry geometry)
298 UseEffect(newShader);
302 renderer = Renderer::New(geometry, shader);
306 textureSet = TextureSet::New();
309 renderer.SetTextures(textureSet);
310 renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE, DepthWriteMode::ON);
311 actor.AddRenderer(renderer);
315 void PageTurnView::Page::ChangeTurnDirection()
317 isTurnBack = !isTurnBack;
318 actor.SetProperty(propertyTurnDirection, isTurnBack ? 1.f : -1.f);
321 void PageTurnView::Page::SetPanDisplacement(float value)
323 actor.SetProperty(propertyPanDisplacement, value);
326 void PageTurnView::Page::SetPanCenter(const Vector2& value)
328 actor.SetProperty(propertyPanCenter, value);
331 void PageTurnView::Page::SetOriginalCenter(const Vector2& value)
333 actor.SetProperty(propertyOriginalCenter, value);
336 void PageTurnView::Page::SetCurrentCenter(const Vector2& value)
338 actor.SetProperty(propertyCurrentCenter, value);
341 PageTurnView::PageTurnView(PageFactory& pageFactory, const Vector2& viewPageSize)
342 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
343 mPageFactory(&pageFactory),
344 mPageSize(viewPageSize),
345 mSpineShadowParameter(DEFAULT_SPINE_SHADOW_PARAMETER),
346 mDistanceUpCorner(0.f),
347 mDistanceBottomCorner(0.f),
348 mPanDisplacement(0.f),
350 mCurrentPageIndex(0),
351 mTurningPageIndex(0),
358 mPageTurnStartedSignal(),
359 mPageTurnFinishedSignal(),
360 mPagePanStartedSignal(),
361 mPagePanFinishedSignal()
365 PageTurnView::~PageTurnView()
369 void PageTurnView::OnInitialize()
371 // create the book spine effect for static pages
372 Property::Map spineEffectMap = CreatePageTurnBookSpineEffect();
373 mSpineEffectShader = CreateShader(spineEffectMap);
374 mSpineEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
375 // create the turn effect for turning pages
376 Property::Map turnEffectMap = CreatePageTurnEffect();
377 mTurnEffectShader = CreateShader(turnEffectMap);
378 mTurnEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
380 // create the grid geometry for pages
381 uint16_t width = static_cast<uint16_t>(mPageSize.width / DEFAULT_GRID_DENSITY + 0.5f);
382 uint16_t height = static_cast<uint16_t>(mPageSize.height / DEFAULT_GRID_DENSITY + 0.5f);
383 mGeometry = VisualFactoryCache::CreateGridGeometry(Uint16Pair(width, height));
385 mPages.reserve(NUMBER_OF_CACHED_PAGES);
386 for(int i = 0; i < NUMBER_OF_CACHED_PAGES; i++)
388 mPages.push_back(Page());
389 mPages[i].actor.SetProperty(Actor::Property::SIZE, mPageSize);
390 Self().Add(mPages[i].actor);
393 // create the layer for turning pages
394 mTurningPageLayer = Layer::New();
395 mTurningPageLayer.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER_LEFT);
396 mTurningPageLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
397 mTurningPageLayer.Raise();
399 // Set control size and the parent origin of page layers
400 OnPageTurnViewInitialize();
402 Self().Add(mTurningPageLayer);
404 mTotalPageCount = static_cast<int>(mPageFactory->GetNumberOfPages());
405 // add pages to the scene, and set depth for the stacked pages
406 for(int i = 0; i < NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
409 mPages[i].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
411 mPages[0].actor.SetProperty(Actor::Property::VISIBLE, true);
413 // enable the pan gesture which is attached to the control
414 EnableGestureDetection(GestureType::Value(GestureType::PAN));
416 DevelControl::SetAccessibilityConstructor(Self(), [](Dali::Actor actor) {
417 return std::unique_ptr<Dali::Accessibility::Accessible>(
418 new Control::Impl::AccessibleImpl(actor, Dali::Accessibility::Role::PAGE_TAB_LIST));
422 Shader PageTurnView::CreateShader(const Property::Map& shaderMap)
425 Property::Value* shaderValue = shaderMap.Find(Toolkit::Visual::Property::SHADER, CUSTOM_SHADER);
426 Property::Map shaderSource;
427 if(shaderValue && shaderValue->Get(shaderSource))
429 std::string vertexShader;
430 Property::Value* vertexShaderValue = shaderSource.Find(Toolkit::Visual::Shader::Property::VERTEX_SHADER, CUSTOM_VERTEX_SHADER);
431 if(!vertexShaderValue || !vertexShaderValue->Get(vertexShader))
433 DALI_LOG_ERROR("PageTurnView::CreateShader failed: vertex shader source is not available.\n");
435 std::string fragmentShader;
436 Property::Value* fragmentShaderValue = shaderSource.Find(Toolkit::Visual::Shader::Property::FRAGMENT_SHADER, CUSTOM_FRAGMENT_SHADER);
437 if(!fragmentShaderValue || !fragmentShaderValue->Get(fragmentShader))
439 DALI_LOG_ERROR("PageTurnView::CreateShader failed: fragment shader source is not available.\n");
441 shader = Shader::New(vertexShader, fragmentShader);
445 DALI_LOG_ERROR("PageTurnView::CreateShader failed: shader source is not available.\n");
451 void PageTurnView::SetupShadowView()
453 mShadowView = Toolkit::ShadowView::New(0.25f, 0.25f);
454 Vector3 origin = mTurningPageLayer.GetCurrentProperty<Vector3>(Actor::Property::PARENT_ORIGIN);
455 mShadowView.SetProperty(Actor::Property::PARENT_ORIGIN, origin);
456 mShadowView.SetProperty(Actor::Property::ANCHOR_POINT, origin);
457 mShadowView.SetPointLightFieldOfView(Math::PI / 2.0f);
458 mShadowView.SetShadowColor(DEFAULT_SHADOW_COLOR);
460 mShadowPlaneBackground = Actor::New();
461 mShadowPlaneBackground.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
462 mShadowPlaneBackground.SetProperty(Actor::Property::SIZE, mControlSize);
463 Self().Add(mShadowPlaneBackground);
464 mShadowView.SetShadowPlaneBackground(mShadowPlaneBackground);
466 mPointLight = Actor::New();
467 mPointLight.SetProperty(Actor::Property::ANCHOR_POINT, origin);
468 mPointLight.SetProperty(Actor::Property::PARENT_ORIGIN, origin);
469 mPointLight.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, mPageSize.width * POINT_LIGHT_HEIGHT_RATIO));
470 Self().Add(mPointLight);
471 mShadowView.SetPointLight(mPointLight);
473 mTurningPageLayer.Add(mShadowView);
474 mShadowView.Activate();
477 void PageTurnView::OnSceneConnection(int depth)
481 Control::OnSceneConnection(depth);
484 void PageTurnView::OnSceneDisconnection()
488 mShadowView.RemoveConstraints();
489 mPointLight.Unparent();
490 mShadowPlaneBackground.Unparent();
491 mShadowView.Unparent();
494 // make sure the status of the control is updated correctly when the pan gesture is interrupted
497 Control::OnSceneDisconnection();
500 void PageTurnView::SetPageSize(const Vector2& viewPageSize)
502 mPageSize = viewPageSize;
506 mPointLight.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, mPageSize.width * POINT_LIGHT_HEIGHT_RATIO));
509 for(size_t i = 0; i < mPages.size(); i++)
511 mPages[i].actor.SetProperty(Actor::Property::SIZE, mPageSize);
514 OnPageTurnViewInitialize();
516 if(mShadowPlaneBackground)
518 mShadowPlaneBackground.SetProperty(Actor::Property::SIZE, mControlSize);
522 Vector2 PageTurnView::GetPageSize()
527 void PageTurnView::SetSpineShadowParameter(const Vector2& spineShadowParameter)
529 mSpineShadowParameter = spineShadowParameter;
531 // set spine shadow parameter to all the shader effects
532 mSpineEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
533 mTurnEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
536 Vector2 PageTurnView::GetSpineShadowParameter()
538 return mSpineShadowParameter;
541 void PageTurnView::GoToPage(unsigned int pageId)
543 int pageIdx = Clamp(static_cast<int>(pageId), 0, mTotalPageCount - 1);
545 if(mCurrentPageIndex == pageIdx)
550 // if any animation ongoing, stop it.
553 // record the new current page index
554 mCurrentPageIndex = pageIdx;
556 // add the current page and the pages right before and after it
557 for(int i = pageIdx - NUMBER_OF_CACHED_PAGES_EACH_SIDE; i < pageIdx + NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
562 mPages[pageId % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
565 mPages[(pageId - 1) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
567 // set ordered depth to the stacked pages
571 unsigned int PageTurnView::GetCurrentPage()
573 DALI_ASSERT_ALWAYS(mCurrentPageIndex >= 0);
574 return static_cast<unsigned int>(mCurrentPageIndex);
577 void PageTurnView::AddPage(int pageIndex)
579 if(pageIndex > -1 && pageIndex < mTotalPageCount) // whether the page is available from the page factory
581 int index = pageIndex % NUMBER_OF_CACHED_PAGES;
584 newPage = mPageFactory->NewPage(pageIndex);
585 DALI_ASSERT_ALWAYS(newPage && "must pass in valid texture");
587 bool isLeftSide = (pageIndex < mCurrentPageIndex);
588 if(mPages[index].isTurnBack != isLeftSide)
590 mPages[index].ChangeTurnDirection();
593 float degree = isLeftSide ? 180.f : 0.f;
594 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
595 mPages[index].actor.SetProperty(Actor::Property::VISIBLE, false);
596 mPages[index].UseEffect(mSpineEffectShader, mGeometry);
597 mPages[index].SetTexture(newPage);
599 // For Portrait, nothing to do
600 // For Landscape, set the parent origin to CENTER
601 OnAddPage(mPages[index].actor, isLeftSide);
605 void PageTurnView::RemovePage(int pageIndex)
607 if(pageIndex > -1 && pageIndex < mTotalPageCount)
609 int index = pageIndex % NUMBER_OF_CACHED_PAGES;
610 mPages[index].actor.SetProperty(Actor::Property::VISIBLE, false);
614 void PageTurnView::OnPan(const PanGesture& gesture)
616 // the pan gesture is attached to control itself instead of each page
617 switch(gesture.GetState())
619 case GestureState::STARTED:
621 // check whether the undergoing turning page number already reaches the maximum allowed
622 if(mPageUpdated && mAnimatingCount < MAXIMUM_TURNING_NUM && mSlidingCount < 1)
624 const Vector2& position = gesture.GetPosition();
625 SetPanActor(position); // determine which page actor is panned
626 if(mTurningPageIndex != -1 && mPages[mTurningPageIndex % NUMBER_OF_CACHED_PAGES].actor.GetParent() != Self()) // if the page is added to turning layer,it is undergoing an animation currently
628 mTurningPageIndex = -1;
630 PanStarted(SetPanPosition(position)); // pass in the pan position in the local page coordinate
634 mTurningPageIndex = -1;
638 case GestureState::CONTINUING:
640 PanContinuing(SetPanPosition(gesture.GetPosition())); // pass in the pan position in the local page coordinate
643 case GestureState::FINISHED:
644 case GestureState::CANCELLED:
646 PanFinished(SetPanPosition(gesture.GetPosition()), gesture.GetSpeed());
649 case GestureState::CLEAR:
650 case GestureState::POSSIBLE:
658 void PageTurnView::PanStarted(const Vector2& gesturePosition)
660 mPressDownPosition = gesturePosition;
662 if(mTurningPageIndex == -1)
667 mIndex = mTurningPageIndex % NUMBER_OF_CACHED_PAGES;
669 mOriginalCenter = gesturePosition;
671 mPageUpdated = false;
673 // Guard against destruction during signal emission
674 Toolkit::PageTurnView handle(GetOwner());
675 mPagePanStartedSignal.Emit(handle);
678 void PageTurnView::PanContinuing(const Vector2& gesturePosition)
680 if(mTurningPageIndex == -1)
685 // Guard against destruction during signal emission
686 Toolkit::PageTurnView handle(GetOwner());
690 // when the touch down position is near the spine
691 // or when the panning goes outwards or some other position which would tear the paper in real situation
692 // we change the start position into the current panning position and update the shader parameters
693 if(mOriginalCenter.x < mPageSize.width * MINIMUM_START_POSITION_RATIO || gesturePosition.x > mOriginalCenter.x - 1.0f || ((gesturePosition.x / mOriginalCenter.x > gesturePosition.y / mOriginalCenter.y) && (gesturePosition.x / mOriginalCenter.x > (gesturePosition.y - mPageSize.height) / (mOriginalCenter.y - mPageSize.height))))
695 mOriginalCenter = gesturePosition;
699 mDistanceUpCorner = mOriginalCenter.Length();
700 mDistanceBottomCorner = (mOriginalCenter - Vector2(0.0f, mPageSize.height)).Length();
701 mShadowView.Add(mPages[mIndex].actor);
702 mPages[mIndex].UseEffect(mTurnEffectShader);
703 mPages[mIndex].SetOriginalCenter(mOriginalCenter);
704 mCurrentCenter = mOriginalCenter;
705 mPages[mIndex].SetCurrentCenter(mCurrentCenter);
706 mPanDisplacement = 0.f;
707 mConstraints = false;
711 mPageTurnStartedSignal.Emit(handle, static_cast<unsigned int>(mTurningPageIndex), !mPages[mIndex].isTurnBack);
712 int id = mTurningPageIndex + (mPages[mIndex].isTurnBack ? -1 : 1);
713 if(id >= 0 && id < mTotalPageCount)
715 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
718 mShadowView.RemoveConstraints();
720 mPages[mIndex].SetPanDisplacement(0.f);
722 Constraint shadowBlurStrengthConstraint = Constraint::New<float>(mShadowView, mShadowView.GetBlurStrengthPropertyIndex(), ShadowBlurStrengthConstraint(mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO));
723 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter));
724 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter));
725 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
726 shadowBlurStrengthConstraint.Apply();
731 Vector2 currentCenter = gesturePosition;
733 // Test whether the new current center would tear the paper from the top pine in real situation
734 // we do not forbid this totally, which would restrict the panning gesture too much
735 // instead, set it to the nearest allowable position
736 float distanceUpCorner = currentCenter.Length();
737 float distanceBottomCorner = (currentCenter - Vector2(0.0f, mPageSize.height)).Length();
738 if(distanceUpCorner > mDistanceUpCorner)
740 currentCenter = currentCenter * mDistanceUpCorner / distanceUpCorner;
742 // would tear the paper from the bottom spine in real situation
743 if(distanceBottomCorner > mDistanceBottomCorner)
745 currentCenter = ((currentCenter - Vector2(0.0f, mPageSize.height)) * mDistanceBottomCorner / distanceBottomCorner + Vector2(0.0f, mPageSize.height));
747 // If direction has a very high y component, reduce it.
748 Vector2 curveDirection = currentCenter - mOriginalCenter;
749 if(fabs(curveDirection.y) > fabs(curveDirection.x))
751 currentCenter.y = mOriginalCenter.y + (currentCenter.y - mOriginalCenter.y) * fabs(curveDirection.x / curveDirection.y);
753 // If the vertical distance is high, reduce it
754 float yShift = currentCenter.y - mOriginalCenter.y;
755 if(fabs(yShift) > mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO)
757 currentCenter.y = mOriginalCenter.y + yShift * mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO / fabs(yShift);
760 // use contraints to control the page shape and rotation when the pan position is near the spine
761 if(currentCenter.x <= mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO && mOriginalCenter.x > mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO)
763 // set the property values used by the constraints
764 mPanDisplacement = mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO - currentCenter.x;
765 mPages[mIndex].SetPanDisplacement(mPanDisplacement);
766 mPages[mIndex].SetPanCenter(currentCenter);
768 // set up the OriginalCenterConstraint and CurrentCebterConstraint to the PageTurnEdffect
769 // also set up the RotationConstraint to the page actor
773 // the corner position need to be a little far away from the page edge to ensure the whole page is lift up
774 if(currentCenter.y >= mOriginalCenter.y)
776 corner = Vector2(1.1f * mPageSize.width, 0.f);
780 corner = mPageSize * 1.1f;
783 Vector2 offset(currentCenter - mOriginalCenter);
784 float k = -((mOriginalCenter.x - corner.x) * offset.x + (mOriginalCenter.y - corner.y) * offset.y) / (offset.x * offset.x + offset.y * offset.y);
788 Constraint originalCenterConstraint = Constraint::New<Vector2>(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter, OriginalCenterConstraint(mOriginalCenter, offset));
789 originalCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
790 originalCenterConstraint.Apply();
792 Constraint currentCenterConstraint = Constraint::New<Vector2>(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter, CurrentCenterConstraint(mPageSize.width));
793 currentCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanCenter));
794 currentCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter));
795 currentCenterConstraint.Apply();
797 PageTurnApplyInternalConstraint(mPages[mIndex].actor, mPageSize.height);
799 float distance = offset.Length();
800 Constraint rotationConstraint = Constraint::New<Quaternion>(mPages[mIndex].actor, Actor::Property::ORIENTATION, RotationConstraint(distance, mPageSize.width, mPages[mIndex].isTurnBack));
801 rotationConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
802 rotationConstraint.Apply();
809 if(mConstraints) // remove the constraint is the pan position move back to far away from the spine
811 mPages[mIndex].actor.RemoveConstraints();
812 mPages[mIndex].SetOriginalCenter(mOriginalCenter);
813 mConstraints = false;
814 mPanDisplacement = 0.f;
817 mPages[mIndex].SetCurrentCenter(currentCenter);
818 mCurrentCenter = currentCenter;
819 PageTurnApplyInternalConstraint(mPages[mIndex].actor, mPageSize.height);
824 void PageTurnView::PanFinished(const Vector2& gesturePosition, float gestureSpeed)
826 // Guard against destruction during signal emission
827 Toolkit::PageTurnView handle(GetOwner());
829 if(mTurningPageIndex == -1)
831 if(mAnimatingCount < MAXIMUM_TURNING_NUM && mSlidingCount < 1)
833 OnPossibleOutwardsFlick(gesturePosition, gestureSpeed);
839 mPagePanFinishedSignal.Emit(handle);
843 if(mConstraints) // if with constraints, the pan finished position is near spine, set up an animation to turn the page over
845 // update the pages here instead of in the TurnedOver callback function
846 // as new page is allowed to respond to the pan gesture before other pages finishing animation
847 if(mPages[mIndex].isTurnBack)
850 RemovePage(mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE);
851 AddPage(mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE);
856 RemovePage(mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE - 1);
857 AddPage(mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE - 1);
861 // set up an animation to turn the page over
862 float width = mPageSize.width * (1.f + PAGE_TURN_OVER_THRESHOLD_RATIO);
863 Animation animation = Animation::New(std::max(0.1f, PAGE_TURN_OVER_ANIMATION_DURATION * (1.0f - mPanDisplacement / width)));
864 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement),
866 AlphaFunction::EASE_OUT_SINE);
867 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyPanCenter),
868 Vector2(-mPageSize.width * 1.1f, 0.5f * mPageSize.height),
869 AlphaFunction::EASE_OUT_SINE);
870 mAnimationPageIdPair[animation] = mTurningPageIndex;
872 animation.FinishedSignal().Connect(this, &PageTurnView::TurnedOver);
874 else // the pan finished position is far away from the spine, set up an animation to slide the page back instead of turning over
876 Animation animation = Animation::New(PAGE_SLIDE_BACK_ANIMATION_DURATION * (mOriginalCenter.x - mCurrentCenter.x) / mPageSize.width / PAGE_TURN_OVER_THRESHOLD_RATIO);
877 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter),
879 AlphaFunction::LINEAR);
880 mAnimationPageIdPair[animation] = mTurningPageIndex;
883 animation.FinishedSignal().Connect(this, &PageTurnView::SliddenBack);
885 mPageTurnStartedSignal.Emit(handle, static_cast<unsigned int>(mTurningPageIndex), mPages[mIndex].isTurnBack);
890 // In portrait view, an outwards flick should turn the previous page back
891 // In landscape view, nothing to do
892 OnPossibleOutwardsFlick(gesturePosition, gestureSpeed);
897 void PageTurnView::TurnedOver(Animation& animation)
899 int pageId = mAnimationPageIdPair[animation];
900 int index = pageId % NUMBER_OF_CACHED_PAGES;
902 mPages[index].ChangeTurnDirection();
903 mPages[index].actor.RemoveConstraints();
904 Self().Add(mPages[index].actor);
906 mAnimationPageIdPair.erase(animation);
908 float degree = mPages[index].isTurnBack ? 180.f : 0.f;
909 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
910 mPages[index].UseEffect(mSpineEffectShader);
912 int id = pageId + (mPages[index].isTurnBack ? -1 : 1);
913 if(id >= 0 && id < mTotalPageCount)
915 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, false);
918 OnTurnedOver(mPages[index].actor, mPages[index].isTurnBack);
920 // Guard against destruction during signal emission
921 Toolkit::PageTurnView handle(GetOwner());
922 mPageTurnFinishedSignal.Emit(handle, static_cast<unsigned int>(pageId), mPages[index].isTurnBack);
925 void PageTurnView::SliddenBack(Animation& animation)
927 int pageId = mAnimationPageIdPair[animation];
928 int index = pageId % NUMBER_OF_CACHED_PAGES;
929 Self().Add(mPages[index].actor);
932 mAnimationPageIdPair.erase(animation);
934 mPages[index].UseEffect(mSpineEffectShader);
936 int id = pageId + (mPages[index].isTurnBack ? -1 : 1);
937 if(id >= 0 && id < mTotalPageCount)
939 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, false);
942 // Guard against destruction during signal emission
943 Toolkit::PageTurnView handle(GetOwner());
944 mPageTurnFinishedSignal.Emit(handle, static_cast<unsigned int>(pageId), mPages[index].isTurnBack);
947 void PageTurnView::OrganizePageDepth()
949 for(int i = 0; i < NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
951 if(mCurrentPageIndex + i < mTotalPageCount)
953 mPages[(mCurrentPageIndex + i) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
955 if(mCurrentPageIndex >= i + 1)
957 mPages[(mCurrentPageIndex - i - 1) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
962 void PageTurnView::StopTurning()
969 int index = mTurningPageIndex % NUMBER_OF_CACHED_PAGES;
970 Self().Add(mPages[index].actor);
971 mPages[index].actor.RemoveConstraints();
972 mPages[index].UseEffect(mSpineEffectShader);
973 float degree = mTurningPageIndex == mCurrentPageIndex ? 0.f : 180.f;
974 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
978 if(!mAnimationPageIdPair.empty())
980 for(std::map<Animation, int>::iterator it = mAnimationPageIdPair.begin(); it != mAnimationPageIdPair.end(); ++it)
982 static_cast<Animation>(it->first).SetCurrentProgress(1.f);
987 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnStartedSignal()
989 return mPageTurnStartedSignal;
992 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnFinishedSignal()
994 return mPageTurnFinishedSignal;
997 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanStartedSignal()
999 return mPagePanStartedSignal;
1002 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanFinishedSignal()
1004 return mPagePanFinishedSignal;
1007 bool PageTurnView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1009 Dali::BaseHandle handle(object);
1011 bool connected(true);
1012 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(handle);
1014 if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_TURN_STARTED))
1016 pageTurnView.PageTurnStartedSignal().Connect(tracker, functor);
1018 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_TURN_FINISHED))
1020 pageTurnView.PageTurnFinishedSignal().Connect(tracker, functor);
1022 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_PAN_STARTED))
1024 pageTurnView.PagePanStartedSignal().Connect(tracker, functor);
1026 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_PAN_FINISHED))
1028 pageTurnView.PagePanFinishedSignal().Connect(tracker, functor);
1032 // signalName does not match any signal
1039 void PageTurnView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
1041 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(Dali::BaseHandle(object));
1045 PageTurnView& pageTurnViewImpl(GetImplementation(pageTurnView));
1049 case Toolkit::PageTurnView::Property::VIEW_PAGE_SIZE:
1051 pageTurnViewImpl.SetPageSize(value.Get<Vector2>());
1054 case Toolkit::PageTurnView::Property::CURRENT_PAGE_ID:
1056 pageTurnViewImpl.GoToPage(value.Get<int>());
1059 case Toolkit::PageTurnView::Property::SPINE_SHADOW:
1061 pageTurnViewImpl.SetSpineShadowParameter(value.Get<Vector2>());
1068 Property::Value PageTurnView::GetProperty(BaseObject* object, Property::Index index)
1070 Property::Value value;
1072 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(Dali::BaseHandle(object));
1076 PageTurnView& pageTurnViewImpl(GetImplementation(pageTurnView));
1080 case Toolkit::PageTurnView::Property::VIEW_PAGE_SIZE:
1082 value = pageTurnViewImpl.GetPageSize();
1085 case Toolkit::PageTurnView::Property::CURRENT_PAGE_ID:
1087 value = static_cast<int>(pageTurnViewImpl.GetCurrentPage());
1090 case Toolkit::PageTurnView::Property::SPINE_SHADOW:
1092 value = pageTurnViewImpl.GetSpineShadowParameter();
1100 } // namespace Internal
1102 } // namespace Toolkit