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/devel-api/controls/control-devel.h>
31 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
32 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-book-spine-effect.h>
33 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-effect.h>
34 #include <dali-toolkit/internal/visuals/visual-factory-cache.h>
35 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
36 #include <dali-toolkit/public-api/visuals/visual-properties.h>
40 namespace //Unnamed namespace
42 // properties set on shader, these properties have the constant value in regardless of the page status
43 const char* const PROPERTY_SPINE_SHADOW("uSpineShadowParameter"); // uniform for both spine and turn effect
45 // properties set on actor, the value of these properties varies depending on the page status
46 // properties used in turn effect
47 const char* const PROPERTY_TURN_DIRECTION("uIsTurningBack"); // uniform
48 const char* const PROPERTY_COMMON_PARAMETERS("uCommonParameters"); //uniform
50 const char* const PROPERTY_PAN_DISPLACEMENT("panDisplacement"); // property used to constrain the uniforms
51 const char* const PROPERTY_PAN_CENTER("panCenter"); // property used to constrain the uniforms
53 // default grid density for page turn effect, 20 pixels by 20 pixels
54 const float DEFAULT_GRID_DENSITY(20.0f);
56 // to bent the page, the minimal horizontal pan start position is viewPageSize.x * MINIMUM_START_POSITION_RATIO
57 const float MINIMUM_START_POSITION_RATIO(0.6f);
59 // the maximum vertical displacement of pan gesture, if exceed, will reduce it: viewPageSize.y * MAXIMUM_VERTICAL_MOVEMENT_RATIO
60 const float MAXIMUM_VERTICAL_MOVEMENT_RATIO(0.15f);
62 // when the x component of pan position reaches viewPageSize.x * PAGE_TURN_OVER_THRESHOLD_RATIO, page starts to turn over
63 const float PAGE_TURN_OVER_THRESHOLD_RATIO(0.5f);
65 // duration of animation, shorter for faster speed
66 const float PAGE_SLIDE_BACK_ANIMATION_DURATION(1.0f);
67 const float PAGE_TURN_OVER_ANIMATION_DURATION(1.2f);
69 // the major&minor radius (in pixels) to form an ellipse shape
70 // the top-left quarter of this ellipse is used to calculate spine normal for simulating shadow
71 const Vector2 DEFAULT_SPINE_SHADOW_PARAMETER(50.0f, 20.0f);
73 // constants for shadow casting
74 const float POINT_LIGHT_HEIGHT_RATIO(2.f);
75 const Vector4 DEFAULT_SHADOW_COLOR = Vector4(0.2f, 0.2f, 0.2f, 0.5f);
77 // constraints ////////////////////////////////////////////////////////////////
79 * Original Center Constraint
81 * This constraint adjusts the original center property of the page turn shader effect
82 * based on the X-direction displacement of the pan gesture
84 struct OriginalCenterConstraint
86 OriginalCenterConstraint(const Vector2& originalCenter, const Vector2& offset)
87 : mOldCenter(originalCenter)
89 mNewCenter = originalCenter + offset;
90 mDistance = offset.Length() * 0.5f;
91 mDirection = offset / mDistance;
94 void operator()(Vector2& current, const PropertyInputContainer& inputs)
96 float displacement = inputs[0]->GetFloat();
98 if(displacement < mDistance)
100 current = mOldCenter + mDirection * displacement;
104 current = mNewCenter + Vector2(0.25f * (displacement - mDistance), 0.f);
115 * Rotation Constraint
117 * This constraint adjusts the rotation property of the page actor
118 * based on the X-direction displacement of the pan gesture
120 struct RotationConstraint
122 RotationConstraint(float distance, float pageWidth, bool isTurnBack)
123 : mDistance(distance * 0.5f)
125 mStep = 1.f / pageWidth;
126 mSign = isTurnBack ? -1.0f : 1.0f;
127 mConst = isTurnBack ? -1.0f : 0.f;
128 mRotation = isTurnBack ? Quaternion(Radian(-Math::PI), Vector3::YAXIS) : Quaternion(Radian(0.f), Vector3::YAXIS);
131 void operator()(Quaternion& current, const PropertyInputContainer& inputs)
133 float displacement = inputs[0]->GetFloat();
134 if(displacement < mDistance)
140 float coef = std::max(-1.0f, mStep * (mDistance - displacement));
141 float angle = Math::PI * (mConst + mSign * coef);
142 current = Quaternion(Radian(angle), Vector3::YAXIS);
150 Quaternion mRotation;
154 * Current Center Constraint
156 * This constraint adjusts the current center property of the page turn shader effect
157 * based on the pan position and the original center position
159 struct CurrentCenterConstraint
161 CurrentCenterConstraint(float pageWidth)
162 : mPageWidth(pageWidth)
164 mThres = pageWidth * PAGE_TURN_OVER_THRESHOLD_RATIO * 0.5f;
167 void operator()(Vector2& current, const PropertyInputContainer& inputs)
169 const Vector2& centerPosition = inputs[0]->GetVector2();
170 if(centerPosition.x > 0.f)
172 current.x = mThres + centerPosition.x * 0.5f;
173 current.y = centerPosition.y;
177 const Vector2& centerOrigin = inputs[1]->GetVector2();
178 Vector2 direction = centerOrigin - Vector2(mThres, centerPosition.y);
179 float coef = 1.f + (centerPosition.x * 2.f / mPageWidth);
180 // when coef <= 0, the page is flat, slow down the last moment of the page stretch by 10 times to avoid a small bounce
183 coef = (coef + 0.225f) / 10.0f;
185 current = centerOrigin - direction * coef;
193 struct ShadowBlurStrengthConstraint
195 ShadowBlurStrengthConstraint(float thres)
200 void operator()(float& blurStrength, const PropertyInputContainer& inputs)
202 float displacement = inputs[2]->GetFloat();
203 if(EqualsZero(displacement))
205 const Vector2& cur = inputs[0]->GetVector2();
206 const Vector2& ori = inputs[1]->GetVector2();
207 blurStrength = 5.f * (ori - cur).Length() / mThres;
211 blurStrength = 1.f - (displacement - 2.f * mThres) / mThres;
214 blurStrength = blurStrength > 1.f ? 1.f : (blurStrength < 0.f ? 0.f : blurStrength);
220 } //unnamed namespace
232 // empty handle as we cannot create PageTurnView(but type registered for page turn signal)
236 // Setup properties, signals and actions using the type-registry.
237 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::PageTurnView, Toolkit::Control, Create);
239 DALI_PROPERTY_REGISTRATION(Toolkit, PageTurnView, "viewPageSize", VECTOR2, VIEW_PAGE_SIZE)
240 DALI_PROPERTY_REGISTRATION(Toolkit, PageTurnView, "currentPageId", INTEGER, CURRENT_PAGE_ID)
241 DALI_PROPERTY_REGISTRATION(Toolkit, PageTurnView, "spineShadow", VECTOR2, SPINE_SHADOW)
243 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pageTurnStarted", SIGNAL_PAGE_TURN_STARTED)
244 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pageTurnFinished", SIGNAL_PAGE_TURN_FINISHED)
245 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pagePanStarted", SIGNAL_PAGE_PAN_STARTED)
246 DALI_SIGNAL_REGISTRATION(Toolkit, PageTurnView, "pagePanFinished", SIGNAL_PAGE_PAN_FINISHED)
248 DALI_TYPE_REGISTRATION_END()
252 // these several constants are also used in the derived classes
253 const char* const PageTurnView::PROPERTY_TEXTURE_WIDTH("uTextureWidth"); // uniform name
254 const char* const PageTurnView::PROPERTY_ORIGINAL_CENTER("originalCenter"); // property used to constrain the uniform
255 const char* const PageTurnView::PROPERTY_CURRENT_CENTER("currentCenter"); // property used to constrain the uniform
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;
261 PageTurnView::Page::Page()
264 actor = Actor::New();
265 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER_LEFT);
266 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER_LEFT);
267 actor.SetProperty(Actor::Property::VISIBLE, false);
269 propertyPanDisplacement = actor.RegisterProperty(PROPERTY_PAN_DISPLACEMENT, 0.f);
270 propertyPanCenter = actor.RegisterProperty(PROPERTY_PAN_CENTER, Vector2::ZERO);
272 propertyOriginalCenter = actor.RegisterProperty(PROPERTY_ORIGINAL_CENTER, Vector2::ZERO);
273 propertyCurrentCenter = actor.RegisterProperty(PROPERTY_CURRENT_CENTER, Vector2::ZERO);
274 Matrix zeroMatrix(true);
275 actor.RegisterProperty(PROPERTY_COMMON_PARAMETERS, zeroMatrix);
276 propertyTurnDirection = actor.RegisterProperty(PROPERTY_TURN_DIRECTION, -1.f);
279 void PageTurnView::Page::SetTexture(Texture texture)
283 textureSet = TextureSet::New();
285 textureSet.SetTexture(0u, texture);
288 void PageTurnView::Page::UseEffect(Shader newShader)
293 renderer.SetShader(shader);
297 void PageTurnView::Page::UseEffect(Shader newShader, Geometry geometry)
299 UseEffect(newShader);
303 renderer = Renderer::New(geometry, shader);
307 textureSet = TextureSet::New();
310 renderer.SetTextures(textureSet);
311 renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE, DepthWriteMode::ON);
312 actor.AddRenderer(renderer);
316 void PageTurnView::Page::ChangeTurnDirection()
318 isTurnBack = !isTurnBack;
319 actor.SetProperty(propertyTurnDirection, isTurnBack ? 1.f : -1.f);
322 void PageTurnView::Page::SetPanDisplacement(float value)
324 actor.SetProperty(propertyPanDisplacement, value);
327 void PageTurnView::Page::SetPanCenter(const Vector2& value)
329 actor.SetProperty(propertyPanCenter, value);
332 void PageTurnView::Page::SetOriginalCenter(const Vector2& value)
334 actor.SetProperty(propertyOriginalCenter, value);
337 void PageTurnView::Page::SetCurrentCenter(const Vector2& value)
339 actor.SetProperty(propertyCurrentCenter, value);
342 PageTurnView::PageTurnView(PageFactory& pageFactory, const Vector2& viewPageSize)
343 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
344 mPageFactory(&pageFactory),
345 mPageSize(viewPageSize),
346 mSpineShadowParameter(DEFAULT_SPINE_SHADOW_PARAMETER),
347 mDistanceUpCorner(0.f),
348 mDistanceBottomCorner(0.f),
349 mPanDisplacement(0.f),
351 mCurrentPageIndex(0),
352 mTurningPageIndex(0),
359 mPageTurnStartedSignal(),
360 mPageTurnFinishedSignal(),
361 mPagePanStartedSignal(),
362 mPagePanFinishedSignal()
366 PageTurnView::~PageTurnView()
370 void PageTurnView::OnInitialize()
372 // create the book spine effect for static pages
373 Property::Map spineEffectMap = CreatePageTurnBookSpineEffect();
374 mSpineEffectShader = CreateShader(spineEffectMap);
375 mSpineEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
376 // create the turn effect for turning pages
377 Property::Map turnEffectMap = CreatePageTurnEffect();
378 mTurnEffectShader = CreateShader(turnEffectMap);
379 mTurnEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
381 // create the grid geometry for pages
382 uint16_t width = static_cast<uint16_t>(mPageSize.width / DEFAULT_GRID_DENSITY + 0.5f);
383 uint16_t height = static_cast<uint16_t>(mPageSize.height / DEFAULT_GRID_DENSITY + 0.5f);
384 mGeometry = VisualFactoryCache::CreateGridGeometry(Uint16Pair(width, height));
386 mPages.reserve(NUMBER_OF_CACHED_PAGES);
387 for(int i = 0; i < NUMBER_OF_CACHED_PAGES; i++)
389 mPages.push_back(Page());
390 mPages[i].actor.SetProperty(Actor::Property::SIZE, mPageSize);
391 Self().Add(mPages[i].actor);
394 // create the layer for turning pages
395 mTurningPageLayer = Layer::New();
396 mTurningPageLayer.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER_LEFT);
397 mTurningPageLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
398 mTurningPageLayer.Raise();
400 // Set control size and the parent origin of page layers
401 OnPageTurnViewInitialize();
403 Self().Add(mTurningPageLayer);
405 mTotalPageCount = static_cast<int>(mPageFactory->GetNumberOfPages());
406 // add pages to the scene, and set depth for the stacked pages
407 for(int i = 0; i < NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
410 mPages[i].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
412 mPages[0].actor.SetProperty(Actor::Property::VISIBLE, true);
414 // enable the pan gesture which is attached to the control
415 EnableGestureDetection(GestureType::Value(GestureType::PAN));
417 Self().SetProperty(DevelControl::Property::ACCESSIBILITY_ROLE, Dali::Accessibility::Role::PAGE_TAB_LIST);
420 Shader PageTurnView::CreateShader(const Property::Map& shaderMap)
423 Property::Value* shaderValue = shaderMap.Find(Toolkit::Visual::Property::SHADER, CUSTOM_SHADER);
424 Property::Map shaderSource;
425 if(shaderValue && shaderValue->Get(shaderSource))
427 std::string vertexShader;
428 Property::Value* vertexShaderValue = shaderSource.Find(Toolkit::Visual::Shader::Property::VERTEX_SHADER, CUSTOM_VERTEX_SHADER);
429 if(!vertexShaderValue || !vertexShaderValue->Get(vertexShader))
431 DALI_LOG_ERROR("PageTurnView::CreateShader failed: vertex shader source is not available.\n");
433 std::string fragmentShader;
434 Property::Value* fragmentShaderValue = shaderSource.Find(Toolkit::Visual::Shader::Property::FRAGMENT_SHADER, CUSTOM_FRAGMENT_SHADER);
435 if(!fragmentShaderValue || !fragmentShaderValue->Get(fragmentShader))
437 DALI_LOG_ERROR("PageTurnView::CreateShader failed: fragment shader source is not available.\n");
439 shader = Shader::New(vertexShader, fragmentShader);
443 DALI_LOG_ERROR("PageTurnView::CreateShader failed: shader source is not available.\n");
449 void PageTurnView::SetupShadowView()
451 mShadowView = Toolkit::ShadowView::New(0.25f, 0.25f);
452 Vector3 origin = mTurningPageLayer.GetCurrentProperty<Vector3>(Actor::Property::PARENT_ORIGIN);
453 mShadowView.SetProperty(Actor::Property::PARENT_ORIGIN, origin);
454 mShadowView.SetProperty(Actor::Property::ANCHOR_POINT, origin);
455 mShadowView.SetPointLightFieldOfView(Math::PI / 2.0f);
456 mShadowView.SetShadowColor(DEFAULT_SHADOW_COLOR);
458 mShadowPlaneBackground = Actor::New();
459 mShadowPlaneBackground.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
460 mShadowPlaneBackground.SetProperty(Actor::Property::SIZE, mControlSize);
461 Self().Add(mShadowPlaneBackground);
462 mShadowView.SetShadowPlaneBackground(mShadowPlaneBackground);
464 mPointLight = Actor::New();
465 mPointLight.SetProperty(Actor::Property::ANCHOR_POINT, origin);
466 mPointLight.SetProperty(Actor::Property::PARENT_ORIGIN, origin);
467 mPointLight.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, mPageSize.width * POINT_LIGHT_HEIGHT_RATIO));
468 Self().Add(mPointLight);
469 mShadowView.SetPointLight(mPointLight);
471 mTurningPageLayer.Add(mShadowView);
472 mShadowView.Activate();
475 void PageTurnView::OnSceneConnection(int depth)
479 Control::OnSceneConnection(depth);
482 void PageTurnView::OnSceneDisconnection()
486 mShadowView.RemoveConstraints();
487 mPointLight.Unparent();
488 mShadowPlaneBackground.Unparent();
489 mShadowView.Unparent();
492 // make sure the status of the control is updated correctly when the pan gesture is interrupted
495 Control::OnSceneDisconnection();
498 void PageTurnView::SetPageSize(const Vector2& viewPageSize)
500 mPageSize = viewPageSize;
504 mPointLight.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, mPageSize.width * POINT_LIGHT_HEIGHT_RATIO));
507 for(size_t i = 0; i < mPages.size(); i++)
509 mPages[i].actor.SetProperty(Actor::Property::SIZE, mPageSize);
512 OnPageTurnViewInitialize();
514 if(mShadowPlaneBackground)
516 mShadowPlaneBackground.SetProperty(Actor::Property::SIZE, mControlSize);
520 Vector2 PageTurnView::GetPageSize()
525 void PageTurnView::SetSpineShadowParameter(const Vector2& spineShadowParameter)
527 mSpineShadowParameter = spineShadowParameter;
529 // set spine shadow parameter to all the shader effects
530 mSpineEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
531 mTurnEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
534 Vector2 PageTurnView::GetSpineShadowParameter()
536 return mSpineShadowParameter;
539 void PageTurnView::GoToPage(unsigned int pageId)
541 int pageIdx = Clamp(static_cast<int>(pageId), 0, mTotalPageCount - 1);
543 if(mCurrentPageIndex == pageIdx)
548 // if any animation ongoing, stop it.
551 // record the new current page index
552 mCurrentPageIndex = pageIdx;
554 // add the current page and the pages right before and after it
555 for(int i = pageIdx - NUMBER_OF_CACHED_PAGES_EACH_SIDE; i < pageIdx + NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
560 mPages[pageId % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
563 mPages[(pageId - 1) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
565 // set ordered depth to the stacked pages
569 unsigned int PageTurnView::GetCurrentPage()
571 DALI_ASSERT_ALWAYS(mCurrentPageIndex >= 0);
572 return static_cast<unsigned int>(mCurrentPageIndex);
575 void PageTurnView::AddPage(int pageIndex)
577 if(pageIndex > -1 && pageIndex < mTotalPageCount) // whether the page is available from the page factory
579 int index = pageIndex % NUMBER_OF_CACHED_PAGES;
582 newPage = mPageFactory->NewPage(pageIndex);
583 DALI_ASSERT_ALWAYS(newPage && "must pass in valid texture");
585 bool isLeftSide = (pageIndex < mCurrentPageIndex);
586 if(mPages[index].isTurnBack != isLeftSide)
588 mPages[index].ChangeTurnDirection();
591 float degree = isLeftSide ? 180.f : 0.f;
592 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
593 mPages[index].actor.SetProperty(Actor::Property::VISIBLE, false);
594 mPages[index].UseEffect(mSpineEffectShader, mGeometry);
595 mPages[index].SetTexture(newPage);
597 // For Portrait, nothing to do
598 // For Landscape, set the parent origin to CENTER
599 OnAddPage(mPages[index].actor, isLeftSide);
603 void PageTurnView::RemovePage(int pageIndex)
605 if(pageIndex > -1 && pageIndex < mTotalPageCount)
607 int index = pageIndex % NUMBER_OF_CACHED_PAGES;
608 mPages[index].actor.SetProperty(Actor::Property::VISIBLE, false);
612 void PageTurnView::OnPan(const PanGesture& gesture)
614 // the pan gesture is attached to control itself instead of each page
615 switch(gesture.GetState())
617 case GestureState::STARTED:
619 // check whether the undergoing turning page number already reaches the maximum allowed
620 if(mPageUpdated && mAnimatingCount < MAXIMUM_TURNING_NUM && mSlidingCount < 1)
622 const Vector2& position = gesture.GetPosition();
623 SetPanActor(position); // determine which page actor is panned
624 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
626 mTurningPageIndex = -1;
628 PanStarted(SetPanPosition(position)); // pass in the pan position in the local page coordinate
632 mTurningPageIndex = -1;
636 case GestureState::CONTINUING:
638 PanContinuing(SetPanPosition(gesture.GetPosition())); // pass in the pan position in the local page coordinate
641 case GestureState::FINISHED:
642 case GestureState::CANCELLED:
644 PanFinished(SetPanPosition(gesture.GetPosition()), gesture.GetSpeed());
647 case GestureState::CLEAR:
648 case GestureState::POSSIBLE:
656 void PageTurnView::PanStarted(const Vector2& gesturePosition)
658 mPressDownPosition = gesturePosition;
660 if(mTurningPageIndex == -1)
665 mIndex = mTurningPageIndex % NUMBER_OF_CACHED_PAGES;
667 mOriginalCenter = gesturePosition;
669 mPageUpdated = false;
671 // Guard against destruction during signal emission
672 Toolkit::PageTurnView handle(GetOwner());
673 mPagePanStartedSignal.Emit(handle);
676 void PageTurnView::PanContinuing(const Vector2& gesturePosition)
678 if(mTurningPageIndex == -1)
683 // Guard against destruction during signal emission
684 Toolkit::PageTurnView handle(GetOwner());
688 // when the touch down position is near the spine
689 // or when the panning goes outwards or some other position which would tear the paper in real situation
690 // we change the start position into the current panning position and update the shader parameters
691 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))))
693 mOriginalCenter = gesturePosition;
697 mDistanceUpCorner = mOriginalCenter.Length();
698 mDistanceBottomCorner = (mOriginalCenter - Vector2(0.0f, mPageSize.height)).Length();
699 mShadowView.Add(mPages[mIndex].actor);
700 mPages[mIndex].UseEffect(mTurnEffectShader);
701 mPages[mIndex].SetOriginalCenter(mOriginalCenter);
702 mCurrentCenter = mOriginalCenter;
703 mPages[mIndex].SetCurrentCenter(mCurrentCenter);
704 mPanDisplacement = 0.f;
705 mConstraints = false;
709 mPageTurnStartedSignal.Emit(handle, static_cast<unsigned int>(mTurningPageIndex), !mPages[mIndex].isTurnBack);
710 int id = mTurningPageIndex + (mPages[mIndex].isTurnBack ? -1 : 1);
711 if(id >= 0 && id < mTotalPageCount)
713 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
716 mShadowView.RemoveConstraints();
718 mPages[mIndex].SetPanDisplacement(0.f);
720 Constraint shadowBlurStrengthConstraint = Constraint::New<float>(mShadowView, mShadowView.GetBlurStrengthPropertyIndex(), ShadowBlurStrengthConstraint(mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO));
721 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter));
722 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter));
723 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
724 shadowBlurStrengthConstraint.Apply();
729 Vector2 currentCenter = gesturePosition;
731 // Test whether the new current center would tear the paper from the top pine in real situation
732 // we do not forbid this totally, which would restrict the panning gesture too much
733 // instead, set it to the nearest allowable position
734 float distanceUpCorner = currentCenter.Length();
735 float distanceBottomCorner = (currentCenter - Vector2(0.0f, mPageSize.height)).Length();
736 if(distanceUpCorner > mDistanceUpCorner)
738 currentCenter = currentCenter * mDistanceUpCorner / distanceUpCorner;
740 // would tear the paper from the bottom spine in real situation
741 if(distanceBottomCorner > mDistanceBottomCorner)
743 currentCenter = ((currentCenter - Vector2(0.0f, mPageSize.height)) * mDistanceBottomCorner / distanceBottomCorner + Vector2(0.0f, mPageSize.height));
745 // If direction has a very high y component, reduce it.
746 Vector2 curveDirection = currentCenter - mOriginalCenter;
747 if(fabs(curveDirection.y) > fabs(curveDirection.x))
749 currentCenter.y = mOriginalCenter.y + (currentCenter.y - mOriginalCenter.y) * fabs(curveDirection.x / curveDirection.y);
751 // If the vertical distance is high, reduce it
752 float yShift = currentCenter.y - mOriginalCenter.y;
753 if(fabs(yShift) > mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO)
755 currentCenter.y = mOriginalCenter.y + yShift * mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO / fabs(yShift);
758 // use contraints to control the page shape and rotation when the pan position is near the spine
759 if(currentCenter.x <= mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO && mOriginalCenter.x > mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO)
761 // set the property values used by the constraints
762 mPanDisplacement = mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO - currentCenter.x;
763 mPages[mIndex].SetPanDisplacement(mPanDisplacement);
764 mPages[mIndex].SetPanCenter(currentCenter);
766 // set up the OriginalCenterConstraint and CurrentCebterConstraint to the PageTurnEdffect
767 // also set up the RotationConstraint to the page actor
771 // the corner position need to be a little far away from the page edge to ensure the whole page is lift up
772 if(currentCenter.y >= mOriginalCenter.y)
774 corner = Vector2(1.1f * mPageSize.width, 0.f);
778 corner = mPageSize * 1.1f;
781 Vector2 offset(currentCenter - mOriginalCenter);
782 float k = -((mOriginalCenter.x - corner.x) * offset.x + (mOriginalCenter.y - corner.y) * offset.y) / (offset.x * offset.x + offset.y * offset.y);
786 Constraint originalCenterConstraint = Constraint::New<Vector2>(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter, OriginalCenterConstraint(mOriginalCenter, offset));
787 originalCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
788 originalCenterConstraint.Apply();
790 Constraint currentCenterConstraint = Constraint::New<Vector2>(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter, CurrentCenterConstraint(mPageSize.width));
791 currentCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanCenter));
792 currentCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter));
793 currentCenterConstraint.Apply();
795 PageTurnApplyInternalConstraint(mPages[mIndex].actor, mPageSize.height);
797 float distance = offset.Length();
798 Constraint rotationConstraint = Constraint::New<Quaternion>(mPages[mIndex].actor, Actor::Property::ORIENTATION, RotationConstraint(distance, mPageSize.width, mPages[mIndex].isTurnBack));
799 rotationConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
800 rotationConstraint.Apply();
807 if(mConstraints) // remove the constraint is the pan position move back to far away from the spine
809 mPages[mIndex].actor.RemoveConstraints();
810 mPages[mIndex].SetOriginalCenter(mOriginalCenter);
811 mConstraints = false;
812 mPanDisplacement = 0.f;
815 mPages[mIndex].SetCurrentCenter(currentCenter);
816 mCurrentCenter = currentCenter;
817 PageTurnApplyInternalConstraint(mPages[mIndex].actor, mPageSize.height);
822 void PageTurnView::PanFinished(const Vector2& gesturePosition, float gestureSpeed)
824 // Guard against destruction during signal emission
825 Toolkit::PageTurnView handle(GetOwner());
827 if(mTurningPageIndex == -1)
829 if(mAnimatingCount < MAXIMUM_TURNING_NUM && mSlidingCount < 1)
831 OnPossibleOutwardsFlick(gesturePosition, gestureSpeed);
837 mPagePanFinishedSignal.Emit(handle);
841 if(mConstraints) // if with constraints, the pan finished position is near spine, set up an animation to turn the page over
843 // update the pages here instead of in the TurnedOver callback function
844 // as new page is allowed to respond to the pan gesture before other pages finishing animation
845 if(mPages[mIndex].isTurnBack)
848 RemovePage(mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE);
849 AddPage(mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE);
854 RemovePage(mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE - 1);
855 AddPage(mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE - 1);
859 // set up an animation to turn the page over
860 float width = mPageSize.width * (1.f + PAGE_TURN_OVER_THRESHOLD_RATIO);
861 Animation animation = Animation::New(std::max(0.1f, PAGE_TURN_OVER_ANIMATION_DURATION * (1.0f - mPanDisplacement / width)));
862 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement),
864 AlphaFunction::EASE_OUT_SINE);
865 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyPanCenter),
866 Vector2(-mPageSize.width * 1.1f, 0.5f * mPageSize.height),
867 AlphaFunction::EASE_OUT_SINE);
868 mAnimationPageIdPair[animation] = mTurningPageIndex;
870 animation.FinishedSignal().Connect(this, &PageTurnView::TurnedOver);
872 else // the pan finished position is far away from the spine, set up an animation to slide the page back instead of turning over
874 Animation animation = Animation::New(PAGE_SLIDE_BACK_ANIMATION_DURATION * (mOriginalCenter.x - mCurrentCenter.x) / mPageSize.width / PAGE_TURN_OVER_THRESHOLD_RATIO);
875 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter),
877 AlphaFunction::LINEAR);
878 mAnimationPageIdPair[animation] = mTurningPageIndex;
881 animation.FinishedSignal().Connect(this, &PageTurnView::SliddenBack);
883 mPageTurnStartedSignal.Emit(handle, static_cast<unsigned int>(mTurningPageIndex), mPages[mIndex].isTurnBack);
888 // In portrait view, an outwards flick should turn the previous page back
889 // In landscape view, nothing to do
890 OnPossibleOutwardsFlick(gesturePosition, gestureSpeed);
895 void PageTurnView::TurnedOver(Animation& animation)
897 int pageId = mAnimationPageIdPair[animation];
898 int index = pageId % NUMBER_OF_CACHED_PAGES;
900 mPages[index].ChangeTurnDirection();
901 mPages[index].actor.RemoveConstraints();
902 Self().Add(mPages[index].actor);
904 mAnimationPageIdPair.erase(animation);
906 float degree = mPages[index].isTurnBack ? 180.f : 0.f;
907 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
908 mPages[index].UseEffect(mSpineEffectShader);
910 int id = pageId + (mPages[index].isTurnBack ? -1 : 1);
911 if(id >= 0 && id < mTotalPageCount)
913 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, false);
916 OnTurnedOver(mPages[index].actor, mPages[index].isTurnBack);
918 // Guard against destruction during signal emission
919 Toolkit::PageTurnView handle(GetOwner());
920 mPageTurnFinishedSignal.Emit(handle, static_cast<unsigned int>(pageId), mPages[index].isTurnBack);
923 void PageTurnView::SliddenBack(Animation& animation)
925 int pageId = mAnimationPageIdPair[animation];
926 int index = pageId % NUMBER_OF_CACHED_PAGES;
927 Self().Add(mPages[index].actor);
930 mAnimationPageIdPair.erase(animation);
932 mPages[index].UseEffect(mSpineEffectShader);
934 int id = pageId + (mPages[index].isTurnBack ? -1 : 1);
935 if(id >= 0 && id < mTotalPageCount)
937 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, false);
940 // Guard against destruction during signal emission
941 Toolkit::PageTurnView handle(GetOwner());
942 mPageTurnFinishedSignal.Emit(handle, static_cast<unsigned int>(pageId), mPages[index].isTurnBack);
945 void PageTurnView::OrganizePageDepth()
947 for(int i = 0; i < NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
949 if(mCurrentPageIndex + i < mTotalPageCount)
951 mPages[(mCurrentPageIndex + i) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
953 if(mCurrentPageIndex >= i + 1)
955 mPages[(mCurrentPageIndex - i - 1) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
960 void PageTurnView::StopTurning()
967 int index = mTurningPageIndex % NUMBER_OF_CACHED_PAGES;
968 Self().Add(mPages[index].actor);
969 mPages[index].actor.RemoveConstraints();
970 mPages[index].UseEffect(mSpineEffectShader);
971 float degree = mTurningPageIndex == mCurrentPageIndex ? 0.f : 180.f;
972 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
976 if(!mAnimationPageIdPair.empty())
978 for(std::map<Animation, int>::iterator it = mAnimationPageIdPair.begin(); it != mAnimationPageIdPair.end(); ++it)
980 static_cast<Animation>(it->first).SetCurrentProgress(1.f);
985 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnStartedSignal()
987 return mPageTurnStartedSignal;
990 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnFinishedSignal()
992 return mPageTurnFinishedSignal;
995 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanStartedSignal()
997 return mPagePanStartedSignal;
1000 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanFinishedSignal()
1002 return mPagePanFinishedSignal;
1005 bool PageTurnView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1007 Dali::BaseHandle handle(object);
1009 bool connected(true);
1010 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(handle);
1012 if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_TURN_STARTED))
1014 pageTurnView.PageTurnStartedSignal().Connect(tracker, functor);
1016 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_TURN_FINISHED))
1018 pageTurnView.PageTurnFinishedSignal().Connect(tracker, functor);
1020 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_PAN_STARTED))
1022 pageTurnView.PagePanStartedSignal().Connect(tracker, functor);
1024 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_PAN_FINISHED))
1026 pageTurnView.PagePanFinishedSignal().Connect(tracker, functor);
1030 // signalName does not match any signal
1037 void PageTurnView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
1039 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(Dali::BaseHandle(object));
1043 PageTurnView& pageTurnViewImpl(GetImplementation(pageTurnView));
1047 case Toolkit::PageTurnView::Property::VIEW_PAGE_SIZE:
1049 pageTurnViewImpl.SetPageSize(value.Get<Vector2>());
1052 case Toolkit::PageTurnView::Property::CURRENT_PAGE_ID:
1054 pageTurnViewImpl.GoToPage(value.Get<int>());
1057 case Toolkit::PageTurnView::Property::SPINE_SHADOW:
1059 pageTurnViewImpl.SetSpineShadowParameter(value.Get<Vector2>());
1066 Property::Value PageTurnView::GetProperty(BaseObject* object, Property::Index index)
1068 Property::Value value;
1070 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(Dali::BaseHandle(object));
1074 PageTurnView& pageTurnViewImpl(GetImplementation(pageTurnView));
1078 case Toolkit::PageTurnView::Property::VIEW_PAGE_SIZE:
1080 value = pageTurnViewImpl.GetPageSize();
1083 case Toolkit::PageTurnView::Property::CURRENT_PAGE_ID:
1085 value = static_cast<int>(pageTurnViewImpl.GetCurrentPage());
1088 case Toolkit::PageTurnView::Property::SPINE_SHADOW:
1090 value = pageTurnViewImpl.GetSpineShadowParameter();
1098 } // namespace Internal
1100 } // namespace Toolkit