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 DevelControl::SetAccessibilityConstructor(Self(), [](Dali::Actor actor) {
418 return std::unique_ptr<Dali::Accessibility::Accessible>(
419 new DevelControl::AccessibleImpl(actor, Dali::Accessibility::Role::PAGE_TAB_LIST));
423 Shader PageTurnView::CreateShader(const Property::Map& shaderMap)
426 Property::Value* shaderValue = shaderMap.Find(Toolkit::Visual::Property::SHADER, CUSTOM_SHADER);
427 Property::Map shaderSource;
428 if(shaderValue && shaderValue->Get(shaderSource))
430 std::string vertexShader;
431 Property::Value* vertexShaderValue = shaderSource.Find(Toolkit::Visual::Shader::Property::VERTEX_SHADER, CUSTOM_VERTEX_SHADER);
432 if(!vertexShaderValue || !vertexShaderValue->Get(vertexShader))
434 DALI_LOG_ERROR("PageTurnView::CreateShader failed: vertex shader source is not available.\n");
436 std::string fragmentShader;
437 Property::Value* fragmentShaderValue = shaderSource.Find(Toolkit::Visual::Shader::Property::FRAGMENT_SHADER, CUSTOM_FRAGMENT_SHADER);
438 if(!fragmentShaderValue || !fragmentShaderValue->Get(fragmentShader))
440 DALI_LOG_ERROR("PageTurnView::CreateShader failed: fragment shader source is not available.\n");
442 shader = Shader::New(vertexShader, fragmentShader);
446 DALI_LOG_ERROR("PageTurnView::CreateShader failed: shader source is not available.\n");
452 void PageTurnView::SetupShadowView()
454 mShadowView = Toolkit::ShadowView::New(0.25f, 0.25f);
455 Vector3 origin = mTurningPageLayer.GetCurrentProperty<Vector3>(Actor::Property::PARENT_ORIGIN);
456 mShadowView.SetProperty(Actor::Property::PARENT_ORIGIN, origin);
457 mShadowView.SetProperty(Actor::Property::ANCHOR_POINT, origin);
458 mShadowView.SetPointLightFieldOfView(Math::PI / 2.0f);
459 mShadowView.SetShadowColor(DEFAULT_SHADOW_COLOR);
461 mShadowPlaneBackground = Actor::New();
462 mShadowPlaneBackground.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
463 mShadowPlaneBackground.SetProperty(Actor::Property::SIZE, mControlSize);
464 Self().Add(mShadowPlaneBackground);
465 mShadowView.SetShadowPlaneBackground(mShadowPlaneBackground);
467 mPointLight = Actor::New();
468 mPointLight.SetProperty(Actor::Property::ANCHOR_POINT, origin);
469 mPointLight.SetProperty(Actor::Property::PARENT_ORIGIN, origin);
470 mPointLight.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, mPageSize.width * POINT_LIGHT_HEIGHT_RATIO));
471 Self().Add(mPointLight);
472 mShadowView.SetPointLight(mPointLight);
474 mTurningPageLayer.Add(mShadowView);
475 mShadowView.Activate();
478 void PageTurnView::OnSceneConnection(int depth)
482 Control::OnSceneConnection(depth);
485 void PageTurnView::OnSceneDisconnection()
489 mShadowView.RemoveConstraints();
490 mPointLight.Unparent();
491 mShadowPlaneBackground.Unparent();
492 mShadowView.Unparent();
495 // make sure the status of the control is updated correctly when the pan gesture is interrupted
498 Control::OnSceneDisconnection();
501 void PageTurnView::SetPageSize(const Vector2& viewPageSize)
503 mPageSize = viewPageSize;
507 mPointLight.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, mPageSize.width * POINT_LIGHT_HEIGHT_RATIO));
510 for(size_t i = 0; i < mPages.size(); i++)
512 mPages[i].actor.SetProperty(Actor::Property::SIZE, mPageSize);
515 OnPageTurnViewInitialize();
517 if(mShadowPlaneBackground)
519 mShadowPlaneBackground.SetProperty(Actor::Property::SIZE, mControlSize);
523 Vector2 PageTurnView::GetPageSize()
528 void PageTurnView::SetSpineShadowParameter(const Vector2& spineShadowParameter)
530 mSpineShadowParameter = spineShadowParameter;
532 // set spine shadow parameter to all the shader effects
533 mSpineEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
534 mTurnEffectShader.RegisterProperty(PROPERTY_SPINE_SHADOW, mSpineShadowParameter);
537 Vector2 PageTurnView::GetSpineShadowParameter()
539 return mSpineShadowParameter;
542 void PageTurnView::GoToPage(unsigned int pageId)
544 int pageIdx = Clamp(static_cast<int>(pageId), 0, mTotalPageCount - 1);
546 if(mCurrentPageIndex == pageIdx)
551 // if any animation ongoing, stop it.
554 // record the new current page index
555 mCurrentPageIndex = pageIdx;
557 // add the current page and the pages right before and after it
558 for(int i = pageIdx - NUMBER_OF_CACHED_PAGES_EACH_SIDE; i < pageIdx + NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
563 mPages[pageId % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
566 mPages[(pageId - 1) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
568 // set ordered depth to the stacked pages
572 unsigned int PageTurnView::GetCurrentPage()
574 DALI_ASSERT_ALWAYS(mCurrentPageIndex >= 0);
575 return static_cast<unsigned int>(mCurrentPageIndex);
578 void PageTurnView::AddPage(int pageIndex)
580 if(pageIndex > -1 && pageIndex < mTotalPageCount) // whether the page is available from the page factory
582 int index = pageIndex % NUMBER_OF_CACHED_PAGES;
585 newPage = mPageFactory->NewPage(pageIndex);
586 DALI_ASSERT_ALWAYS(newPage && "must pass in valid texture");
588 bool isLeftSide = (pageIndex < mCurrentPageIndex);
589 if(mPages[index].isTurnBack != isLeftSide)
591 mPages[index].ChangeTurnDirection();
594 float degree = isLeftSide ? 180.f : 0.f;
595 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
596 mPages[index].actor.SetProperty(Actor::Property::VISIBLE, false);
597 mPages[index].UseEffect(mSpineEffectShader, mGeometry);
598 mPages[index].SetTexture(newPage);
600 // For Portrait, nothing to do
601 // For Landscape, set the parent origin to CENTER
602 OnAddPage(mPages[index].actor, isLeftSide);
606 void PageTurnView::RemovePage(int pageIndex)
608 if(pageIndex > -1 && pageIndex < mTotalPageCount)
610 int index = pageIndex % NUMBER_OF_CACHED_PAGES;
611 mPages[index].actor.SetProperty(Actor::Property::VISIBLE, false);
615 void PageTurnView::OnPan(const PanGesture& gesture)
617 // the pan gesture is attached to control itself instead of each page
618 switch(gesture.GetState())
620 case GestureState::STARTED:
622 // check whether the undergoing turning page number already reaches the maximum allowed
623 if(mPageUpdated && mAnimatingCount < MAXIMUM_TURNING_NUM && mSlidingCount < 1)
625 const Vector2& position = gesture.GetPosition();
626 SetPanActor(position); // determine which page actor is panned
627 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
629 mTurningPageIndex = -1;
631 PanStarted(SetPanPosition(position)); // pass in the pan position in the local page coordinate
635 mTurningPageIndex = -1;
639 case GestureState::CONTINUING:
641 PanContinuing(SetPanPosition(gesture.GetPosition())); // pass in the pan position in the local page coordinate
644 case GestureState::FINISHED:
645 case GestureState::CANCELLED:
647 PanFinished(SetPanPosition(gesture.GetPosition()), gesture.GetSpeed());
650 case GestureState::CLEAR:
651 case GestureState::POSSIBLE:
659 void PageTurnView::PanStarted(const Vector2& gesturePosition)
661 mPressDownPosition = gesturePosition;
663 if(mTurningPageIndex == -1)
668 mIndex = mTurningPageIndex % NUMBER_OF_CACHED_PAGES;
670 mOriginalCenter = gesturePosition;
672 mPageUpdated = false;
674 // Guard against destruction during signal emission
675 Toolkit::PageTurnView handle(GetOwner());
676 mPagePanStartedSignal.Emit(handle);
679 void PageTurnView::PanContinuing(const Vector2& gesturePosition)
681 if(mTurningPageIndex == -1)
686 // Guard against destruction during signal emission
687 Toolkit::PageTurnView handle(GetOwner());
691 // when the touch down position is near the spine
692 // or when the panning goes outwards or some other position which would tear the paper in real situation
693 // we change the start position into the current panning position and update the shader parameters
694 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))))
696 mOriginalCenter = gesturePosition;
700 mDistanceUpCorner = mOriginalCenter.Length();
701 mDistanceBottomCorner = (mOriginalCenter - Vector2(0.0f, mPageSize.height)).Length();
702 mShadowView.Add(mPages[mIndex].actor);
703 mPages[mIndex].UseEffect(mTurnEffectShader);
704 mPages[mIndex].SetOriginalCenter(mOriginalCenter);
705 mCurrentCenter = mOriginalCenter;
706 mPages[mIndex].SetCurrentCenter(mCurrentCenter);
707 mPanDisplacement = 0.f;
708 mConstraints = false;
712 mPageTurnStartedSignal.Emit(handle, static_cast<unsigned int>(mTurningPageIndex), !mPages[mIndex].isTurnBack);
713 int id = mTurningPageIndex + (mPages[mIndex].isTurnBack ? -1 : 1);
714 if(id >= 0 && id < mTotalPageCount)
716 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, true);
719 mShadowView.RemoveConstraints();
721 mPages[mIndex].SetPanDisplacement(0.f);
723 Constraint shadowBlurStrengthConstraint = Constraint::New<float>(mShadowView, mShadowView.GetBlurStrengthPropertyIndex(), ShadowBlurStrengthConstraint(mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO));
724 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter));
725 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter));
726 shadowBlurStrengthConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
727 shadowBlurStrengthConstraint.Apply();
732 Vector2 currentCenter = gesturePosition;
734 // Test whether the new current center would tear the paper from the top pine in real situation
735 // we do not forbid this totally, which would restrict the panning gesture too much
736 // instead, set it to the nearest allowable position
737 float distanceUpCorner = currentCenter.Length();
738 float distanceBottomCorner = (currentCenter - Vector2(0.0f, mPageSize.height)).Length();
739 if(distanceUpCorner > mDistanceUpCorner)
741 currentCenter = currentCenter * mDistanceUpCorner / distanceUpCorner;
743 // would tear the paper from the bottom spine in real situation
744 if(distanceBottomCorner > mDistanceBottomCorner)
746 currentCenter = ((currentCenter - Vector2(0.0f, mPageSize.height)) * mDistanceBottomCorner / distanceBottomCorner + Vector2(0.0f, mPageSize.height));
748 // If direction has a very high y component, reduce it.
749 Vector2 curveDirection = currentCenter - mOriginalCenter;
750 if(fabs(curveDirection.y) > fabs(curveDirection.x))
752 currentCenter.y = mOriginalCenter.y + (currentCenter.y - mOriginalCenter.y) * fabs(curveDirection.x / curveDirection.y);
754 // If the vertical distance is high, reduce it
755 float yShift = currentCenter.y - mOriginalCenter.y;
756 if(fabs(yShift) > mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO)
758 currentCenter.y = mOriginalCenter.y + yShift * mPageSize.height * MAXIMUM_VERTICAL_MOVEMENT_RATIO / fabs(yShift);
761 // use contraints to control the page shape and rotation when the pan position is near the spine
762 if(currentCenter.x <= mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO && mOriginalCenter.x > mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO)
764 // set the property values used by the constraints
765 mPanDisplacement = mPageSize.width * PAGE_TURN_OVER_THRESHOLD_RATIO - currentCenter.x;
766 mPages[mIndex].SetPanDisplacement(mPanDisplacement);
767 mPages[mIndex].SetPanCenter(currentCenter);
769 // set up the OriginalCenterConstraint and CurrentCebterConstraint to the PageTurnEdffect
770 // also set up the RotationConstraint to the page actor
774 // the corner position need to be a little far away from the page edge to ensure the whole page is lift up
775 if(currentCenter.y >= mOriginalCenter.y)
777 corner = Vector2(1.1f * mPageSize.width, 0.f);
781 corner = mPageSize * 1.1f;
784 Vector2 offset(currentCenter - mOriginalCenter);
785 float k = -((mOriginalCenter.x - corner.x) * offset.x + (mOriginalCenter.y - corner.y) * offset.y) / (offset.x * offset.x + offset.y * offset.y);
789 Constraint originalCenterConstraint = Constraint::New<Vector2>(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter, OriginalCenterConstraint(mOriginalCenter, offset));
790 originalCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
791 originalCenterConstraint.Apply();
793 Constraint currentCenterConstraint = Constraint::New<Vector2>(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter, CurrentCenterConstraint(mPageSize.width));
794 currentCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanCenter));
795 currentCenterConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyOriginalCenter));
796 currentCenterConstraint.Apply();
798 PageTurnApplyInternalConstraint(mPages[mIndex].actor, mPageSize.height);
800 float distance = offset.Length();
801 Constraint rotationConstraint = Constraint::New<Quaternion>(mPages[mIndex].actor, Actor::Property::ORIENTATION, RotationConstraint(distance, mPageSize.width, mPages[mIndex].isTurnBack));
802 rotationConstraint.AddSource(Source(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement));
803 rotationConstraint.Apply();
810 if(mConstraints) // remove the constraint is the pan position move back to far away from the spine
812 mPages[mIndex].actor.RemoveConstraints();
813 mPages[mIndex].SetOriginalCenter(mOriginalCenter);
814 mConstraints = false;
815 mPanDisplacement = 0.f;
818 mPages[mIndex].SetCurrentCenter(currentCenter);
819 mCurrentCenter = currentCenter;
820 PageTurnApplyInternalConstraint(mPages[mIndex].actor, mPageSize.height);
825 void PageTurnView::PanFinished(const Vector2& gesturePosition, float gestureSpeed)
827 // Guard against destruction during signal emission
828 Toolkit::PageTurnView handle(GetOwner());
830 if(mTurningPageIndex == -1)
832 if(mAnimatingCount < MAXIMUM_TURNING_NUM && mSlidingCount < 1)
834 OnPossibleOutwardsFlick(gesturePosition, gestureSpeed);
840 mPagePanFinishedSignal.Emit(handle);
844 if(mConstraints) // if with constraints, the pan finished position is near spine, set up an animation to turn the page over
846 // update the pages here instead of in the TurnedOver callback function
847 // as new page is allowed to respond to the pan gesture before other pages finishing animation
848 if(mPages[mIndex].isTurnBack)
851 RemovePage(mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE);
852 AddPage(mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE);
857 RemovePage(mCurrentPageIndex - NUMBER_OF_CACHED_PAGES_EACH_SIDE - 1);
858 AddPage(mCurrentPageIndex + NUMBER_OF_CACHED_PAGES_EACH_SIDE - 1);
862 // set up an animation to turn the page over
863 float width = mPageSize.width * (1.f + PAGE_TURN_OVER_THRESHOLD_RATIO);
864 Animation animation = Animation::New(std::max(0.1f, PAGE_TURN_OVER_ANIMATION_DURATION * (1.0f - mPanDisplacement / width)));
865 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyPanDisplacement),
867 AlphaFunction::EASE_OUT_SINE);
868 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyPanCenter),
869 Vector2(-mPageSize.width * 1.1f, 0.5f * mPageSize.height),
870 AlphaFunction::EASE_OUT_SINE);
871 mAnimationPageIdPair[animation] = mTurningPageIndex;
873 animation.FinishedSignal().Connect(this, &PageTurnView::TurnedOver);
875 else // the pan finished position is far away from the spine, set up an animation to slide the page back instead of turning over
877 Animation animation = Animation::New(PAGE_SLIDE_BACK_ANIMATION_DURATION * (mOriginalCenter.x - mCurrentCenter.x) / mPageSize.width / PAGE_TURN_OVER_THRESHOLD_RATIO);
878 animation.AnimateTo(Property(mPages[mIndex].actor, mPages[mIndex].propertyCurrentCenter),
880 AlphaFunction::LINEAR);
881 mAnimationPageIdPair[animation] = mTurningPageIndex;
884 animation.FinishedSignal().Connect(this, &PageTurnView::SliddenBack);
886 mPageTurnStartedSignal.Emit(handle, static_cast<unsigned int>(mTurningPageIndex), mPages[mIndex].isTurnBack);
891 // In portrait view, an outwards flick should turn the previous page back
892 // In landscape view, nothing to do
893 OnPossibleOutwardsFlick(gesturePosition, gestureSpeed);
898 void PageTurnView::TurnedOver(Animation& animation)
900 int pageId = mAnimationPageIdPair[animation];
901 int index = pageId % NUMBER_OF_CACHED_PAGES;
903 mPages[index].ChangeTurnDirection();
904 mPages[index].actor.RemoveConstraints();
905 Self().Add(mPages[index].actor);
907 mAnimationPageIdPair.erase(animation);
909 float degree = mPages[index].isTurnBack ? 180.f : 0.f;
910 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
911 mPages[index].UseEffect(mSpineEffectShader);
913 int id = pageId + (mPages[index].isTurnBack ? -1 : 1);
914 if(id >= 0 && id < mTotalPageCount)
916 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, false);
919 OnTurnedOver(mPages[index].actor, mPages[index].isTurnBack);
921 // Guard against destruction during signal emission
922 Toolkit::PageTurnView handle(GetOwner());
923 mPageTurnFinishedSignal.Emit(handle, static_cast<unsigned int>(pageId), mPages[index].isTurnBack);
926 void PageTurnView::SliddenBack(Animation& animation)
928 int pageId = mAnimationPageIdPair[animation];
929 int index = pageId % NUMBER_OF_CACHED_PAGES;
930 Self().Add(mPages[index].actor);
933 mAnimationPageIdPair.erase(animation);
935 mPages[index].UseEffect(mSpineEffectShader);
937 int id = pageId + (mPages[index].isTurnBack ? -1 : 1);
938 if(id >= 0 && id < mTotalPageCount)
940 mPages[id % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::VISIBLE, false);
943 // Guard against destruction during signal emission
944 Toolkit::PageTurnView handle(GetOwner());
945 mPageTurnFinishedSignal.Emit(handle, static_cast<unsigned int>(pageId), mPages[index].isTurnBack);
948 void PageTurnView::OrganizePageDepth()
950 for(int i = 0; i < NUMBER_OF_CACHED_PAGES_EACH_SIDE; i++)
952 if(mCurrentPageIndex + i < mTotalPageCount)
954 mPages[(mCurrentPageIndex + i) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
956 if(mCurrentPageIndex >= i + 1)
958 mPages[(mCurrentPageIndex - i - 1) % NUMBER_OF_CACHED_PAGES].actor.SetProperty(Actor::Property::POSITION_Z, -static_cast<float>(i) * STATIC_PAGE_INTERVAL_DISTANCE);
963 void PageTurnView::StopTurning()
970 int index = mTurningPageIndex % NUMBER_OF_CACHED_PAGES;
971 Self().Add(mPages[index].actor);
972 mPages[index].actor.RemoveConstraints();
973 mPages[index].UseEffect(mSpineEffectShader);
974 float degree = mTurningPageIndex == mCurrentPageIndex ? 0.f : 180.f;
975 mPages[index].actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(Degree(degree), Vector3::YAXIS));
979 if(!mAnimationPageIdPair.empty())
981 for(std::map<Animation, int>::iterator it = mAnimationPageIdPair.begin(); it != mAnimationPageIdPair.end(); ++it)
983 static_cast<Animation>(it->first).SetCurrentProgress(1.f);
988 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnStartedSignal()
990 return mPageTurnStartedSignal;
993 Toolkit::PageTurnView::PageTurnSignal& PageTurnView::PageTurnFinishedSignal()
995 return mPageTurnFinishedSignal;
998 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanStartedSignal()
1000 return mPagePanStartedSignal;
1003 Toolkit::PageTurnView::PagePanSignal& PageTurnView::PagePanFinishedSignal()
1005 return mPagePanFinishedSignal;
1008 bool PageTurnView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1010 Dali::BaseHandle handle(object);
1012 bool connected(true);
1013 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(handle);
1015 if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_TURN_STARTED))
1017 pageTurnView.PageTurnStartedSignal().Connect(tracker, functor);
1019 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_TURN_FINISHED))
1021 pageTurnView.PageTurnFinishedSignal().Connect(tracker, functor);
1023 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_PAN_STARTED))
1025 pageTurnView.PagePanStartedSignal().Connect(tracker, functor);
1027 else if(0 == strcmp(signalName.c_str(), SIGNAL_PAGE_PAN_FINISHED))
1029 pageTurnView.PagePanFinishedSignal().Connect(tracker, functor);
1033 // signalName does not match any signal
1040 void PageTurnView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
1042 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(Dali::BaseHandle(object));
1046 PageTurnView& pageTurnViewImpl(GetImplementation(pageTurnView));
1050 case Toolkit::PageTurnView::Property::VIEW_PAGE_SIZE:
1052 pageTurnViewImpl.SetPageSize(value.Get<Vector2>());
1055 case Toolkit::PageTurnView::Property::CURRENT_PAGE_ID:
1057 pageTurnViewImpl.GoToPage(value.Get<int>());
1060 case Toolkit::PageTurnView::Property::SPINE_SHADOW:
1062 pageTurnViewImpl.SetSpineShadowParameter(value.Get<Vector2>());
1069 Property::Value PageTurnView::GetProperty(BaseObject* object, Property::Index index)
1071 Property::Value value;
1073 Toolkit::PageTurnView pageTurnView = Toolkit::PageTurnView::DownCast(Dali::BaseHandle(object));
1077 PageTurnView& pageTurnViewImpl(GetImplementation(pageTurnView));
1081 case Toolkit::PageTurnView::Property::VIEW_PAGE_SIZE:
1083 value = pageTurnViewImpl.GetPageSize();
1086 case Toolkit::PageTurnView::Property::CURRENT_PAGE_ID:
1088 value = static_cast<int>(pageTurnViewImpl.GetCurrentPage());
1091 case Toolkit::PageTurnView::Property::SPINE_SHADOW:
1093 value = pageTurnViewImpl.GetSpineShadowParameter();
1101 } // namespace Internal
1103 } // namespace Toolkit